home *** CD-ROM | disk | FTP | other *** search
/ Amiga Plus 2004 #11 / Amiga Plus CD - 2004 - No. 11.iso / AmiSoft / Comm / www / tidy_os4.lha / tidy / src / parser.c < prev    next >
C/C++ Source or Header  |  2004-07-25  |  118KB  |  4,214 lines

  1. /* parser.c -- HTML Parser
  2.  
  3.   (c) 1998-2004 (W3C) MIT, ERCIM, Keio University
  4.   See tidy.h for the copyright notice.
  5.   
  6.   CVS Info :
  7.  
  8.     $Author: hoehrmann $ 
  9.     $Date: 2004/06/18 21:10:31 $ 
  10.     $Revision: 1.118 $ 
  11.  
  12. */
  13.  
  14. #include "tidy-int.h"
  15. #include "lexer.h"
  16. #include "parser.h"
  17. #include "message.h"
  18. #include "clean.h"
  19. #include "tags.h"
  20. #include "tmbstr.h"
  21.  
  22. #ifdef AUTO_INPUT_ENCODING
  23. #include "charsets.h"
  24. #endif
  25.  
  26. Bool CheckNodeIntegrity(Node *node)
  27. {
  28. #ifndef NO_NODE_INTEGRITY_CHECK
  29.     if (node->prev)
  30.     {
  31.         if (node->prev->next != node)
  32.             return no;
  33.     }
  34.  
  35.     if (node->next)
  36.     {
  37.         if (node->next->prev != node)
  38.             return no;
  39.     }
  40.  
  41.     if (node->parent)
  42.     {
  43.         Node *child = NULL;
  44.         if (node->prev == NULL && node->parent->content != node)
  45.             return no;
  46.  
  47.         if (node->next == NULL && node->parent->last != node)
  48.             return no;
  49.  
  50.         for (child = node->parent->content; child; child = child->next)
  51.         {
  52.             if (child == node)
  53.                 break;
  54.         }
  55.         if ( node != child )
  56.             return no;
  57.     }
  58.  
  59.     for (node = node->content; node; node = node->next)
  60.         if ( !CheckNodeIntegrity(node) )
  61.             return no;
  62.  
  63. #endif
  64.     return yes;
  65. }
  66.  
  67. /*
  68.  used to determine how attributes
  69.  without values should be printed
  70.  this was introduced to deal with
  71.  user defined tags e.g. Cold Fusion
  72. */
  73. Bool IsNewNode(Node *node)
  74. {
  75.     if (node && node->tag)
  76.     {
  77.         return (node->tag->model & CM_NEW);
  78.     }
  79.     return yes;
  80. }
  81.  
  82. void CoerceNode(TidyDocImpl* doc, Node *node, TidyTagId tid, Bool obsolete, Bool expected)
  83. {
  84.     const Dict* tag = LookupTagDef(tid);
  85.     Node* tmp = InferredTag(doc, tag->id);
  86.  
  87.     if (obsolete)
  88.         ReportWarning(doc, node, tmp, OBSOLETE_ELEMENT);
  89.     else if (expected)
  90.         ReportError(doc, node, tmp, REPLACING_UNEX_ELEMENT);
  91.     else
  92.         ReportNotice(doc, node, tmp, REPLACING_ELEMENT);
  93.  
  94.     MemFree(tmp->element);
  95.     MemFree(tmp);
  96.  
  97.     node->was = node->tag;
  98.     node->tag = tag;
  99.     node->type = StartTag;
  100.     node->implicit = yes;
  101.     MemFree(node->element);
  102.     node->element = tmbstrdup(tag->name);
  103. }
  104.  
  105. /* extract a node and its children from a markup tree */
  106. Node *RemoveNode(Node *node)
  107. {
  108.     if (node->prev)
  109.         node->prev->next = node->next;
  110.  
  111.     if (node->next)
  112.         node->next->prev = node->prev;
  113.  
  114.     if (node->parent)
  115.     {
  116.         if (node->parent->content == node)
  117.             node->parent->content = node->next;
  118.  
  119.         if (node->parent->last == node)
  120.             node->parent->last = node->prev;
  121.     }
  122.  
  123.     node->parent = node->prev = node->next = NULL;
  124.     return node;
  125. }
  126.  
  127. /* remove node from markup tree and discard it */
  128. Node *DiscardElement( TidyDocImpl* doc, Node *element )
  129. {
  130.     Node *next = NULL;
  131.  
  132.     if (element)
  133.     {
  134.         next = element->next;
  135.         RemoveNode(element);
  136.         FreeNode( doc, element);
  137.     }
  138.  
  139.     return next;
  140. }
  141.  
  142. /* insert node into markup tree */
  143. void InsertNodeAtStart(Node *element, Node *node)
  144. {
  145.     node->parent = element;
  146.  
  147.     if (element->content == NULL)
  148.         element->last = node;
  149.     else
  150.         element->content->prev = node;
  151.     
  152.     node->next = element->content;
  153.     node->prev = NULL;
  154.     element->content = node;
  155. }
  156.  
  157. /* insert node into markup tree */
  158. void InsertNodeAtEnd(Node *element, Node *node)
  159. {
  160.     node->parent = element;
  161.     node->prev = element->last;
  162.  
  163.     if (element->last != NULL)
  164.         element->last->next = node;
  165.     else
  166.         element->content = node;
  167.  
  168.     element->last = node;
  169. }
  170.  
  171. /*
  172.  insert node into markup tree in place of element
  173.  which is moved to become the child of the node
  174. */
  175. static void InsertNodeAsParent(Node *element, Node *node)
  176. {
  177.     node->content = element;
  178.     node->last = element;
  179.     node->parent = element->parent;
  180.     element->parent = node;
  181.     
  182.     if (node->parent->content == element)
  183.         node->parent->content = node;
  184.  
  185.     if (node->parent->last == element)
  186.         node->parent->last = node;
  187.  
  188.     node->prev = element->prev;
  189.     element->prev = NULL;
  190.  
  191.     if (node->prev)
  192.         node->prev->next = node;
  193.  
  194.     node->next = element->next;
  195.     element->next = NULL;
  196.  
  197.     if (node->next)
  198.         node->next->prev = node;
  199. }
  200.  
  201. /* insert node into markup tree before element */
  202. void InsertNodeBeforeElement(Node *element, Node *node)
  203. {
  204.     Node *parent;
  205.  
  206.     parent = element->parent;
  207.     node->parent = parent;
  208.     node->next = element;
  209.     node->prev = element->prev;
  210.     element->prev = node;
  211.  
  212.     if (node->prev)
  213.         node->prev->next = node;
  214.  
  215.     if (parent->content == element)
  216.         parent->content = node;
  217. }
  218.  
  219. /* insert node into markup tree after element */
  220. void InsertNodeAfterElement(Node *element, Node *node)
  221. {
  222.     Node *parent;
  223.  
  224.     parent = element->parent;
  225.     node->parent = parent;
  226.  
  227.     /* AQ - 13 Jan 2000 fix for parent == NULL */
  228.     if (parent != NULL && parent->last == element)
  229.         parent->last = node;
  230.     else
  231.     {
  232.         node->next = element->next;
  233.         /* AQ - 13 Jan 2000 fix for node->next == NULL */
  234.         if (node->next != NULL)
  235.             node->next->prev = node;
  236.     }
  237.  
  238.     element->next = node;
  239.     node->prev = element;
  240. }
  241.  
  242. static Bool CanPrune( TidyDocImpl* doc, Node *element )
  243. {
  244.     if ( element->type == TextNode )
  245.         return yes;
  246.  
  247.     if ( element->content )
  248.         return no;
  249.  
  250.     if ( element->tag == NULL )
  251.         return no;
  252.  
  253.     if ( element->tag->model & CM_BLOCK && element->attributes != NULL )
  254.         return no;
  255.  
  256.     if ( nodeIsA(element) && element->attributes != NULL )
  257.         return no;
  258.  
  259.     if ( nodeIsP(element) && !cfgBool(doc, TidyDropEmptyParas) )
  260.         return no;
  261.  
  262.     if ( element->tag->model & CM_ROW )
  263.         return no;
  264.  
  265.     if ( element->tag->model & CM_EMPTY )
  266.         return no;
  267.  
  268.     if ( nodeIsAPPLET(element) )
  269.         return no;
  270.  
  271.     if ( nodeIsOBJECT(element) )
  272.         return no;
  273.  
  274.     if ( nodeIsSCRIPT(element) && attrGetSRC(element) )
  275.         return no;
  276.  
  277.     if ( nodeIsTITLE(element) )
  278.         return no;
  279.  
  280.     /* #433359 - fix by Randy Waki 12 Mar 01 */
  281.     if ( nodeIsIFRAME(element) )
  282.         return no;
  283.  
  284.     /* fix for bug 770297 */
  285.     if (nodeIsTEXTAREA(element))
  286.         return no;
  287.  
  288.     if ( attrGetID(element) || attrGetNAME(element) )
  289.         return no;
  290.  
  291.     /* fix for bug 695408; a better fix would look for unknown and    */
  292.     /* known proprietary attributes that make the element significant */
  293.     if (attrGetDATAFLD(element))
  294.         return no;
  295.  
  296.     /* fix for bug 723772, don't trim new-...-tags */
  297.     if (element->tag->id == TidyTag_UNKNOWN)
  298.         return no;
  299.  
  300.     if (nodeIsBODY(element))
  301.         return no;
  302.  
  303.     if (nodeIsCOLGROUP(element))
  304.         return no;
  305.  
  306.     return yes;
  307. }
  308.  
  309. Node *TrimEmptyElement( TidyDocImpl* doc, Node *element )
  310. {
  311.     if ( CanPrune(doc, element) )
  312.     {
  313.        if (element->type != TextNode)
  314.             ReportNotice(doc, element, NULL, TRIM_EMPTY_ELEMENT);
  315.  
  316.         return DiscardElement(doc, element);
  317.     }
  318.     else if ( nodeIsP(element) && element->content == NULL )
  319.     {
  320.         /* Put a non-breaking space into empty paragraphs.
  321.         ** Contrary to intent, replacing empty paragraphs
  322.         ** with two <br><br> does not preserve formatting.
  323.         */
  324.         char onesixty[2] = { '\240', 0 };
  325.         InsertNodeAtStart( element, NewLiteralTextNode(doc->lexer, onesixty) );
  326.     }
  327.     return element;
  328. }
  329.  
  330. static Node* DropEmptyElements(TidyDocImpl* doc, Node* node)
  331. {
  332.     Node* next;
  333.  
  334.     while (node)
  335.     {
  336.         next = node->next;
  337.  
  338.         if (node->content)
  339.             DropEmptyElements(doc, node->content);
  340.  
  341.         if (!nodeIsElement(node) && !(node->type == TextNode && !(node->start < node->end)))
  342.         {
  343.             node = next;
  344.             continue;
  345.         }
  346.  
  347.         next = TrimEmptyElement(doc, node);
  348.         node = node == next ? node->next : next;
  349.     }
  350.  
  351.     return node;
  352. }
  353.  
  354. /* 
  355.   errors in positioning of form start or end tags
  356.   generally require human intervention to fix
  357. */
  358. static void BadForm( TidyDocImpl* doc )
  359. {
  360.     doc->badForm = yes;
  361.     /* doc->errors++; */
  362. }
  363.  
  364. /*
  365.   This maps 
  366.        <em>hello </em><strong>world</strong>
  367.   to
  368.        <em>hello</em> <strong>world</strong>
  369.  
  370.   If last child of element is a text node
  371.   then trim trailing white space character
  372.   moving it to after element's end tag.
  373. */
  374. static void TrimTrailingSpace( TidyDocImpl* doc, Node *element, Node *last )
  375. {
  376.     Lexer* lexer = doc->lexer;
  377.     byte c;
  378.  
  379.     if (last != NULL && last->type == TextNode)
  380.     {
  381.         if (last->end > last->start)
  382.         {
  383.             c = (byte) lexer->lexbuf[ last->end - 1 ];
  384.  
  385.             if (   c == ' '
  386. #ifdef COMMENT_NBSP_FIX
  387.                 || c == 160
  388. #endif
  389.                )
  390.             {
  391. #ifdef COMMENT_NBSP_FIX
  392.                 /* take care with <td> </td> */
  393.                 if ( c == 160 && 
  394.                      ( element->tag == doc->tags.tag_td || 
  395.                        element->tag == doc->tags.tag_th )
  396.                    )
  397.                 {
  398.                     if (last->end > last->start + 1)
  399.                         last->end -= 1;
  400.                 }
  401.                 else
  402. #endif
  403.                 {
  404.                     last->end -= 1;
  405.                     if ( (element->tag->model & CM_INLINE) &&
  406.                          !(element->tag->model & CM_FIELD) )
  407.                         lexer->insertspace = yes;
  408.                 }
  409.             }
  410.         }
  411.     }
  412. }
  413.  
  414. #if 0
  415. static Node *EscapeTag(Lexer *lexer, Node *element)
  416. {
  417.     Node *node = NewNode(lexer);
  418.  
  419.     node->start = lexer->lexsize;
  420.     AddByte(lexer, '<');
  421.  
  422.     if (element->type == EndTag)
  423.         AddByte(lexer, '/');
  424.  
  425.     if (element->element)
  426.     {
  427.         char *p;
  428.         for (p = element->element; *p != '\0'; ++p)
  429.             AddByte(lexer, *p);
  430.     }
  431.     else if (element->type == DocTypeTag)
  432.     {
  433.         uint i;
  434.         AddStringLiteral( lexer, "!DOCTYPE " );
  435.         for (i = element->start; i < element->end; ++i)
  436.             AddByte(lexer, lexer->lexbuf[i]);
  437.     }
  438.  
  439.     if (element->type == StartEndTag)
  440.         AddByte(lexer, '/');
  441.  
  442.     AddByte(lexer, '>');
  443.     node->end = lexer->lexsize;
  444.  
  445.     return node;
  446. }
  447. #endif /* 0 */
  448.  
  449. /* Only true for text nodes. */
  450. Bool IsBlank(Lexer *lexer, Node *node)
  451. {
  452.     Bool isBlank = ( node->type == TextNode );
  453.     if ( isBlank )
  454.         isBlank = ( node->end == node->start ||       /* Zero length */
  455.                     ( node->end == node->start+1      /* or one blank. */
  456.                       && lexer->lexbuf[node->start] == ' ' ) );
  457.     return isBlank;
  458. }
  459.  
  460. /*
  461.   This maps 
  462.        <p>hello<em> world</em>
  463.   to
  464.        <p>hello <em>world</em>
  465.  
  466.   Trims initial space, by moving it before the
  467.   start tag, or if this element is the first in
  468.   parent's content, then by discarding the space
  469. */
  470. static void TrimInitialSpace( TidyDocImpl* doc, Node *element, Node *text )
  471. {
  472.     Lexer* lexer = doc->lexer;
  473.     Node *prev, *node;
  474.  
  475.     if ( text->type == TextNode && 
  476.          lexer->lexbuf[text->start] == ' ' && 
  477.          text->start < text->end )
  478.     {
  479.         if ( (element->tag->model & CM_INLINE) &&
  480.              !(element->tag->model & CM_FIELD) &&
  481.              element->parent->content != element )
  482.         {
  483.             prev = element->prev;
  484.  
  485.             if (prev && prev->type == TextNode)
  486.             {
  487.                 if (lexer->lexbuf[prev->end - 1] != ' ')
  488.                     lexer->lexbuf[(prev->end)++] = ' ';
  489.  
  490.                 ++(element->start);
  491.             }
  492.             else /* create new node */
  493.             {
  494.                 node = NewNode(lexer);
  495.                 node->start = (element->start)++;
  496.                 node->end = element->start;
  497.                 lexer->lexbuf[node->start] = ' ';
  498.                 node->prev = prev;
  499.  
  500.                 if (prev)
  501.                     prev->next = node;
  502.  
  503.                 node->next = element;
  504.                 element->prev = node;
  505.                 node->parent = element->parent;
  506.             }
  507.         }
  508.  
  509.         /* discard the space in current node */
  510.         ++(text->start);
  511.     }
  512. }
  513.  
  514. static Bool IsPreDescendant(Node* node)
  515. {
  516.     Node *parent = node->parent;
  517.  
  518.     while (parent)
  519.     {
  520.         if (parent->tag && parent->tag->parser == ParsePre)
  521.             return yes;
  522.  
  523.         parent = parent->parent;
  524.     }
  525.  
  526.     return no;
  527. }
  528.  
  529. static Bool CleanTrailingWhitespace(TidyDocImpl* doc, Node* node)
  530. {
  531.     Node* next;
  532.  
  533.     if (!nodeIsText(node))
  534.         return no;
  535.  
  536.     if (node->parent->type == DocTypeTag)
  537.         return no;
  538.  
  539.     if (IsPreDescendant(node))
  540.         return no;
  541.  
  542.     if (node->parent->tag->parser == ParseScript)
  543.         return no;
  544.  
  545.     next = node->next;
  546.  
  547.     /* <p>... </p> */
  548.     if (!next && !nodeHasCM(node->parent, CM_INLINE))
  549.         return yes;
  550.  
  551.     /* <div><small>... </small><h3>...</h3></div> */
  552.     if (!next && node->parent->next && !nodeHasCM(node->parent->next, CM_INLINE))
  553.         return yes;
  554.  
  555.     if (!next)
  556.         return no;
  557.  
  558.     if (nodeIsBR(next))
  559.         return yes;
  560.  
  561.     if (nodeHasCM(next, CM_INLINE))
  562.         return no;
  563.  
  564.     /* <a href='/'>...</a> <p>...</p> */
  565.     if (next->type == StartTag)
  566.         return yes;
  567.  
  568.     /* <strong>...</strong> <hr /> */
  569.     if (next->type == StartEndTag)
  570.         return yes;
  571.  
  572.     /* evil adjacent text nodes, Tidy should not generate these :-( */
  573.     if (next->type == TextNode && next->start < next->end
  574.         && IsWhite(doc->lexer->lexbuf[next->start]))
  575.         return yes;
  576.  
  577.     return no;
  578. }
  579.  
  580. static Bool CleanLeadingWhitespace(TidyDocImpl* doc, Node* node)
  581. {
  582. #pragma unused(doc)
  583.  
  584.     if (!nodeIsText(node))
  585.         return no;
  586.  
  587.     if (node->parent->type == DocTypeTag)
  588.         return no;
  589.  
  590.     if (IsPreDescendant(node))
  591.         return no;
  592.  
  593.     if (node->parent->tag->parser == ParseScript)
  594.         return no;
  595.  
  596.     /* <p>...<br> <em>...</em>...</p> */
  597.     if (nodeIsBR(node->prev))
  598.         return yes;
  599.  
  600.     /* <p> ...</p> */
  601.     if (node->prev == NULL && !nodeHasCM(node->parent, CM_INLINE))
  602.         return yes;
  603.  
  604.     /* <h4>...</h4> <em>...</em> */
  605.     if (node->prev && !nodeHasCM(node->prev, CM_INLINE) &&
  606.         (node->prev->type == StartTag || node->prev->type == StartEndTag))
  607.         return yes;
  608.  
  609.     /* <p><span> ...</span></p> */
  610.     if (!node->prev && !node->parent->prev && !nodeHasCM(node->parent->parent, CM_INLINE))
  611.         return yes;
  612.  
  613.     return no;
  614. }
  615.  
  616. static void CleanSpaces(TidyDocImpl* doc, Node* node)
  617. {
  618.     Node* next;
  619.  
  620.     while (node)
  621.     {
  622.         next = node->next;
  623.  
  624.         if (nodeIsText(node) && CleanLeadingWhitespace(doc, node))
  625.             while (node->start < node->end && IsWhite(doc->lexer->lexbuf[node->start]))
  626.                 ++(node->start);
  627.  
  628.         if (nodeIsText(node) && CleanTrailingWhitespace(doc, node))
  629.             while (node->end > node->start && IsWhite(doc->lexer->lexbuf[node->end - 1]))
  630.                 --(node->end);
  631.  
  632.         if (nodeIsText(node) && !(node->start < node->end))
  633.         {
  634.             RemoveNode(node);
  635.             FreeNode(doc, node);
  636.             node = next;
  637.  
  638.             continue;
  639.         }
  640.  
  641.         if (node->content)
  642.             CleanSpaces(doc, node->content);
  643.  
  644.         node = next;
  645.     }
  646. }
  647.  
  648. /* 
  649.   Move initial and trailing space out.
  650.   This routine maps:
  651.  
  652.        hello<em> world</em>
  653.   to
  654.        hello <em>world</em>
  655.   and
  656.        <em>hello </em><strong>world</strong>
  657.   to
  658.        <em>hello</em> <strong>world</strong>
  659. */
  660. static void TrimSpaces( TidyDocImpl* doc, Node *element)
  661. {
  662.     Node* text = element->content;
  663.  
  664.     if (nodeIsPRE(element) || IsPreDescendant(element))
  665.         return;
  666.  
  667.     if (nodeIsText(text))
  668.         TrimInitialSpace(doc, element, text);
  669.  
  670.     text = element->last;
  671.  
  672.     if (nodeIsText(text))
  673.         TrimTrailingSpace(doc, element, text);
  674. }
  675.  
  676. Bool DescendantOf( Node *element, TidyTagId tid )
  677. {
  678.     Node *parent;
  679.     for ( parent = element->parent;
  680.           parent != NULL;
  681.           parent = parent->parent )
  682.     {
  683.         if ( TagIsId(parent, tid) )
  684.             return yes;
  685.     }
  686.     return no;
  687. }
  688.  
  689. static Bool InsertMisc(Node *element, Node *node)
  690. {
  691.     if (node->type == CommentTag ||
  692.         node->type == ProcInsTag ||
  693.         node->type == CDATATag ||
  694.         node->type == SectionTag ||
  695.         node->type == AspTag ||
  696.         node->type == JsteTag ||
  697.         node->type == PhpTag )
  698.     {
  699.         InsertNodeAtEnd(element, node);
  700.         return yes;
  701.     }
  702.  
  703.     if ( node->type == XmlDecl )
  704.     {
  705.         Node* root = element;
  706.         while ( root && root->parent )
  707.             root = root->parent;
  708.         if ( root )
  709.         {
  710.           InsertNodeAtStart( root, node );
  711.           return yes;
  712.         }
  713.     }
  714.  
  715.     /* Declared empty tags seem to be slipping through
  716.     ** the cracks.  This is an experiment to figure out
  717.     ** a decent place to pick them up.
  718.     */
  719.     if ( node->tag &&
  720.          (node->type == StartTag || node->type == StartEndTag) &&
  721.          nodeCMIsEmpty(node) && TagId(node) == TidyTag_UNKNOWN &&
  722.          (node->tag->versions & VERS_PROPRIETARY) != 0 )
  723.     {
  724.         InsertNodeAtEnd(element, node);
  725.         return yes;
  726.     }
  727.  
  728.     return no;
  729. }
  730.  
  731.  
  732. static void ParseTag( TidyDocImpl* doc, Node *node, uint mode )
  733. {
  734.     Lexer* lexer = doc->lexer;
  735.     /*
  736.        Fix by GLP 2000-12-21.  Need to reset insertspace if this 
  737.        is both a non-inline and empty tag (base, link, meta, isindex, hr, area).
  738.     */
  739.     if (node->tag->model & CM_EMPTY)
  740.     {
  741.         lexer->waswhite = no;
  742.         if (node->tag->parser == NULL)
  743.             return;
  744.     }
  745.     else if (!(node->tag->model & CM_INLINE))
  746.         lexer->insertspace = no;
  747.  
  748.     if (node->tag->parser == NULL)
  749.         return;
  750.  
  751.     if (node->type == StartEndTag)
  752.         return;
  753.  
  754.     (*node->tag->parser)( doc, node, mode );
  755. }
  756.  
  757. /*
  758.  the doctype has been found after other tags,
  759.  and needs moving to before the html element
  760. */
  761. static void InsertDocType( TidyDocImpl* doc, Node *element, Node *doctype )
  762. {
  763.     Node* existing = FindDocType( doc );
  764.     if ( existing )
  765.     {
  766.         ReportError(doc, element, doctype, DISCARDING_UNEXPECTED );
  767.         FreeNode( doc, doctype );
  768.     }
  769.     else
  770.     {
  771.         ReportError(doc, element, doctype, DOCTYPE_AFTER_TAGS );
  772.         while ( !nodeIsHTML(element) )
  773.             element = element->parent;
  774.         InsertNodeBeforeElement( element, doctype );
  775.     }
  776. }
  777.  
  778. /*
  779.  move node to the head, where element is used as starting
  780.  point in hunt for head. normally called during parsing
  781. */
  782. static void MoveToHead( TidyDocImpl* doc, Node *element, Node *node )
  783. {
  784.     Node *head;
  785.  
  786.     RemoveNode( node );  /* make sure that node is isolated */
  787.  
  788.     if ( node->type == StartTag || node->type == StartEndTag )
  789.     {
  790.         ReportError(doc, element, node, TAG_NOT_ALLOWED_IN );
  791.  
  792.         head = FindHEAD(doc);
  793.         assert(head != NULL);
  794.  
  795.         InsertNodeAtEnd(head, node);
  796.  
  797.         if ( node->tag->parser )
  798.             ParseTag( doc, node, IgnoreWhitespace );
  799.     }
  800.     else
  801.     {
  802.         ReportError(doc, element, node, DISCARDING_UNEXPECTED);
  803.         FreeNode( doc, node );
  804.     }
  805. }
  806.  
  807. /* moves given node to end of body element */
  808. static void MoveNodeToBody( TidyDocImpl* doc, Node* node )
  809. {
  810.     Node* body = FindBody( doc );
  811.     RemoveNode( node );
  812.     InsertNodeAtEnd( body, node );
  813. }
  814.  
  815. /*
  816.    element is node created by the lexer
  817.    upon seeing the start tag, or by the
  818.    parser when the start tag is inferred
  819. */
  820. void ParseBlock( TidyDocImpl* doc, Node *element, uint mode)
  821. {
  822.     Lexer* lexer = doc->lexer;
  823.     Node *node, *parent;
  824.     Bool checkstack = yes;
  825.     uint istackbase = 0;
  826.  
  827.     if ( element->tag->model & CM_EMPTY )
  828.         return;
  829.  
  830.     if ( nodeIsFORM(element) && 
  831.          DescendantOf(element, TidyTag_FORM) )
  832.         ReportError(doc, element, NULL, ILLEGAL_NESTING );
  833.  
  834.     /*
  835.      InlineDup() asks the lexer to insert inline emphasis tags
  836.      currently pushed on the istack, but take care to avoid
  837.      propagating inline emphasis inside OBJECT or APPLET.
  838.      For these elements a fresh inline stack context is created
  839.      and disposed of upon reaching the end of the element.
  840.      They thus behave like table cells in this respect.
  841.     */
  842.     if (element->tag->model & CM_OBJECT)
  843.     {
  844.         istackbase = lexer->istackbase;
  845.         lexer->istackbase = lexer->istacksize;
  846.     }
  847.  
  848.     if (!(element->tag->model & CM_MIXED))
  849.         InlineDup( doc, NULL );
  850.  
  851.     mode = IgnoreWhitespace;
  852.  
  853.     while ((node = GetToken(doc, mode /*MixedContent*/)) != NULL)
  854.     {
  855.         /* end tag for this element */
  856.         if (node->type == EndTag && node->tag &&
  857.             (node->tag == element->tag || element->was == node->tag))
  858.         {
  859.             FreeNode( doc, node );
  860.  
  861.             if (element->tag->model & CM_OBJECT)
  862.             {
  863.                 /* pop inline stack */
  864.                 while (lexer->istacksize > lexer->istackbase)
  865.                     PopInline( doc, NULL );
  866.                 lexer->istackbase = istackbase;
  867.             }
  868.  
  869.             element->closed = yes;
  870.             TrimSpaces( doc, element );
  871.             return;
  872.         }
  873.  
  874.         if ( nodeIsHTML(node) || nodeIsHEAD(node) || nodeIsBODY(node) )
  875.         {
  876.             if ( node->type == StartTag || node->type == StartEndTag )
  877.                 ReportError(doc, element, node, DISCARDING_UNEXPECTED );
  878.             FreeNode( doc, node );
  879.             continue;
  880.         }
  881.  
  882.  
  883.         if (node->type == EndTag)
  884.         {
  885.             if (node->tag == NULL)
  886.             {
  887.                 ReportError(doc, element, node, DISCARDING_UNEXPECTED );
  888.                 FreeNode( doc, node );
  889.                 continue;
  890.             }
  891.             else if ( nodeIsBR(node) )
  892.                 node->type = StartTag;
  893.             else if ( nodeIsP(node) )
  894.             {
  895.                 CoerceNode(doc, node, TidyTag_BR, no, no);
  896.                 FreeAttrs( doc, node ); /* discard align attribute etc. */
  897.                 InsertNodeAtEnd( element, node );
  898.                 node = InferredTag(doc, TidyTag_BR);
  899.             }
  900.             else
  901.             {
  902.                 /* 
  903.                   if this is the end tag for an ancestor element
  904.                   then infer end tag for this element
  905.                 */
  906.                 for ( parent = element->parent;
  907.                       parent != NULL; 
  908.                       parent = parent->parent )
  909.                 {
  910.                     if (node->tag == parent->tag)
  911.                     {
  912.                         if (!(element->tag->model & CM_OPT))
  913.                             ReportError(doc, element, node, MISSING_ENDTAG_BEFORE );
  914.  
  915.                         UngetToken( doc );
  916.  
  917.                         if (element->tag->model & CM_OBJECT)
  918.                         {
  919.                             /* pop inline stack */
  920.                             while (lexer->istacksize > lexer->istackbase)
  921.                                 PopInline( doc, NULL );
  922.                             lexer->istackbase = istackbase;
  923.                         }
  924.  
  925.                         TrimSpaces( doc, element );
  926.                         return;
  927.                     }
  928.                 }
  929.  
  930.                 /* special case </tr> etc. for stuff moved in front of table */
  931.                 if ( lexer->exiled
  932.                      && node->tag->model
  933.                      && (node->tag->model & CM_TABLE) )
  934.                 {
  935.                     UngetToken( doc );
  936.                     TrimSpaces( doc, element );
  937.                     return;
  938.                 }
  939.             }
  940.         }
  941.  
  942.         /* mixed content model permits text */
  943.         if (node->type == TextNode)
  944.         {
  945.             if ( checkstack )
  946.             {
  947.                 checkstack = no;
  948.                 if (!(element->tag->model & CM_MIXED))
  949.                 {
  950.                     if ( InlineDup(doc, node) > 0 )
  951.                         continue;
  952.                 }
  953.             }
  954.  
  955.             InsertNodeAtEnd(element, node);
  956.             mode = MixedContent;
  957.  
  958.             /*
  959.               HTML4 strict doesn't allow mixed content for
  960.               elements with %block; as their content model
  961.             */
  962.             /*
  963.               But only body, map, blockquote, form and
  964.               noscript have content model %block;
  965.             */
  966.             if ( nodeIsBODY(element)       ||
  967.                  nodeIsMAP(element)        ||
  968.                  nodeIsBLOCKQUOTE(element) ||
  969.                  nodeIsFORM(element)       ||
  970.                  nodeIsNOSCRIPT(element) )
  971.                 ConstrainVersion( doc, ~VERS_HTML40_STRICT );
  972.             continue;
  973.         }
  974.  
  975.         if ( InsertMisc(element, node) )
  976.             continue;
  977.  
  978.         /* allow PARAM elements? */
  979.         if ( nodeIsPARAM(node) )
  980.         {
  981.             if ( nodeHasCM(element, CM_PARAM) &&
  982.                  (node->type == StartEndTag || node->type == StartTag) )
  983.             {
  984.                 InsertNodeAtEnd(element, node);
  985.                 continue;
  986.             }
  987.  
  988.             /* otherwise discard it */
  989.             ReportError(doc, element, node, DISCARDING_UNEXPECTED );
  990.             FreeNode( doc, node );
  991.             continue;
  992.         }
  993.  
  994.         /* allow AREA elements? */
  995.         if ( nodeIsAREA(node) )
  996.         {
  997.             if ( nodeIsMAP(element) &&
  998.                  (node->type == StartTag || node->type == StartEndTag) )
  999.             {
  1000.                 InsertNodeAtEnd(element, node);
  1001.                 continue;
  1002.             }
  1003.  
  1004.             /* otherwise discard it */
  1005.             ReportError(doc, element, node, DISCARDING_UNEXPECTED );
  1006.             FreeNode( doc, node );
  1007.             continue;
  1008.         }
  1009.  
  1010.         /* ignore unknown start/end tags */
  1011.         if ( node->tag == NULL )
  1012.         {
  1013.             ReportError(doc, element, node, DISCARDING_UNEXPECTED );
  1014.             FreeNode( doc, node );
  1015.             continue;
  1016.         }
  1017.  
  1018.         /*
  1019.           Allow CM_INLINE elements here.
  1020.  
  1021.           Allow CM_BLOCK elements here unless
  1022.           lexer->excludeBlocks is yes.
  1023.  
  1024.           LI and DD are special cased.
  1025.  
  1026.           Otherwise infer end tag for this element.
  1027.         */
  1028.  
  1029.         if ( !nodeHasCM(node, CM_INLINE) )
  1030.         {
  1031.             if (node->type != StartTag && node->type != StartEndTag)
  1032.             {
  1033.                 if ( nodeIsFORM(node) )
  1034.                     BadForm( doc );
  1035.  
  1036.                 ReportError(doc, element, node, DISCARDING_UNEXPECTED );
  1037.                 FreeNode( doc, node );
  1038.                 continue;
  1039.             }
  1040.  
  1041.             /* #427671 - Fix by Randy Waki - 10 Aug 00 */
  1042.             /*
  1043.              If an LI contains an illegal FRAME, FRAMESET, OPTGROUP, or OPTION
  1044.              start tag, discard the start tag and let the subsequent content get
  1045.              parsed as content of the enclosing LI.  This seems to mimic IE and
  1046.              Netscape, and avoids an infinite loop: without this check,
  1047.              ParseBlock (which is parsing the LI's content) and ParseList (which
  1048.              is parsing the LI's parent's content) repeatedly defer to each
  1049.              other to parse the illegal start tag, each time inferring a missing
  1050.              </li> or <li> respectively.
  1051.  
  1052.              NOTE: This check is a bit fragile.  It specifically checks for the
  1053.              four tags that happen to weave their way through the current series
  1054.              of tests performed by ParseBlock and ParseList to trigger the
  1055.              infinite loop.
  1056.             */
  1057.             if ( nodeIsLI(element) )
  1058.             {
  1059.                 if ( nodeIsFRAME(node)    ||
  1060.                      nodeIsFRAMESET(node) ||
  1061.                      nodeIsOPTGROUP(node) ||
  1062.                      nodeIsOPTION(node) )
  1063.                 {
  1064.                     ReportError(doc, element, node, DISCARDING_UNEXPECTED );
  1065.                     FreeNode( doc, node );  /* DSR - 27Apr02 avoid memory leak */
  1066.                     continue;
  1067.                 }
  1068.             }
  1069.  
  1070.             if ( nodeIsTD(element) || nodeIsTH(element) )
  1071.             {
  1072.                 /* if parent is a table cell, avoid inferring the end of the cell */
  1073.  
  1074.                 if ( nodeHasCM(node, CM_HEAD) )
  1075.                 {
  1076.                     MoveToHead( doc, element, node );
  1077.                     continue;
  1078.                 }
  1079.  
  1080.                 if ( nodeHasCM(node, CM_LIST) )
  1081.                 {
  1082.                     UngetToken( doc );
  1083.                     node = InferredTag(doc, TidyTag_UL);
  1084.                     AddClass( doc, node, "noindent" );
  1085.                     lexer->excludeBlocks = yes;
  1086.                 }
  1087.                 else if ( nodeHasCM(node, CM_DEFLIST) )
  1088.                 {
  1089.                     UngetToken( doc );
  1090.                     node = InferredTag(doc, TidyTag_DL);
  1091.                     lexer->excludeBlocks = yes;
  1092.                 }
  1093.  
  1094.                 /* infer end of current table cell */
  1095.                 if ( !nodeHasCM(node, CM_BLOCK) )
  1096.                 {
  1097.                     UngetToken( doc );
  1098.                     TrimSpaces( doc, element );
  1099.                     return;
  1100.                 }
  1101.             }
  1102.             else if ( nodeHasCM(node, CM_BLOCK) )
  1103.             {
  1104.                 if ( lexer->excludeBlocks )
  1105.                 {
  1106.                     if ( !nodeHasCM(element, CM_OPT) )
  1107.                         ReportError(doc, element, node, MISSING_ENDTAG_BEFORE );
  1108.  
  1109.                     UngetToken( doc );
  1110.  
  1111.                     if ( nodeHasCM(element, CM_OBJECT) )
  1112.                         lexer->istackbase = istackbase;
  1113.  
  1114.                     TrimSpaces( doc, element );
  1115.                     return;
  1116.                 }
  1117.             }
  1118.             else /* things like list items */
  1119.             {
  1120.                 if (node->tag->model & CM_HEAD)
  1121.                 {
  1122.                     MoveToHead( doc, element, node );
  1123.                     continue;
  1124.                 }
  1125.  
  1126.                 /*
  1127.                  special case where a form start tag
  1128.                  occurs in a tr and is followed by td or th
  1129.                 */
  1130.  
  1131.                 if ( nodeIsFORM(element) &&
  1132.                      nodeIsTD(element->parent) &&
  1133.                      element->parent->implicit )
  1134.                 {
  1135.                     if ( nodeIsTD(node) )
  1136.                     {
  1137.                         ReportError(doc, element, node, DISCARDING_UNEXPECTED );
  1138.                         FreeNode( doc, node );
  1139.                         continue;
  1140.                     }
  1141.  
  1142.                     if ( nodeIsTH(node) )
  1143.                     {
  1144.                         ReportError(doc, element, node, DISCARDING_UNEXPECTED );
  1145.                         FreeNode( doc, node );
  1146.                         node = element->parent;
  1147.                         MemFree(node->element);
  1148.                         node->element = tmbstrdup("th");
  1149.                         node->tag = LookupTagDef( TidyTag_TH );
  1150.                         continue;
  1151.                     }
  1152.                 }
  1153.  
  1154.                 if ( !nodeHasCM(element, CM_OPT) && !element->implicit )
  1155.                     ReportError(doc, element, node, MISSING_ENDTAG_BEFORE );
  1156.  
  1157.                 UngetToken( doc );
  1158.  
  1159.                 if ( nodeHasCM(node, CM_LIST) )
  1160.                 {
  1161.                     if ( element->parent && element->parent->tag &&
  1162.                          element->parent->tag->parser == ParseList )
  1163.                     {
  1164.                         TrimSpaces( doc, element );
  1165.                         return;
  1166.                     }
  1167.  
  1168.                     node = InferredTag(doc, TidyTag_UL);
  1169.                     AddClass( doc, node, "noindent" );
  1170.                 }
  1171.                 else if ( nodeHasCM(node, CM_DEFLIST) )
  1172.                 {
  1173.                     if ( nodeIsDL(element->parent) )
  1174.                     {
  1175.                         TrimSpaces( doc, element );
  1176.                         return;
  1177.                     }
  1178.  
  1179.                     node = InferredTag(doc, TidyTag_DL);
  1180.                 }
  1181.                 else if ( nodeHasCM(node, CM_TABLE) || nodeHasCM(node, CM_ROW) )
  1182.                 {
  1183.                     node = InferredTag(doc, TidyTag_TABLE);
  1184.                 }
  1185.                 else if ( nodeHasCM(element, CM_OBJECT) )
  1186.                 {
  1187.                     /* pop inline stack */
  1188.                     while ( lexer->istacksize > lexer->istackbase )
  1189.                         PopInline( doc, NULL );
  1190.                     lexer->istackbase = istackbase;
  1191.                     TrimSpaces( doc, element );
  1192.                     return;
  1193.  
  1194.                 }
  1195.                 else
  1196.                 {
  1197.                     TrimSpaces( doc, element );
  1198.                     return;
  1199.                 }
  1200.             }
  1201.         }
  1202.  
  1203.         /* parse known element */
  1204.         if (node->type == StartTag || node->type == StartEndTag)
  1205.         {
  1206.             if (node->tag->model & CM_INLINE)
  1207.             {
  1208.                 if (checkstack && !node->implicit)
  1209.                 {
  1210.                     checkstack = no;
  1211.  
  1212.                     if (!(element->tag->model & CM_MIXED)) /* #431731 - fix by Randy Waki 25 Dec 00 */
  1213.                     {
  1214.                         if ( InlineDup(doc, node) > 0 )
  1215.                             continue;
  1216.                     }
  1217.                 }
  1218.  
  1219.                 mode = MixedContent;
  1220.             }
  1221.             else
  1222.             {
  1223.                 checkstack = yes;
  1224.                 mode = IgnoreWhitespace;
  1225.             }
  1226.  
  1227.             /* trim white space before <br> */
  1228.             if ( nodeIsBR(node) )
  1229.                 TrimSpaces( doc, element );
  1230.  
  1231.             InsertNodeAtEnd(element, node);
  1232.             
  1233.             if (node->implicit)
  1234.                 ReportError(doc, element, node, INSERTING_TAG );
  1235.  
  1236.             ParseTag( doc, node, IgnoreWhitespace /*MixedContent*/ );
  1237.             continue;
  1238.         }
  1239.  
  1240.         /* discard unexpected tags */
  1241.         if (node->type == EndTag)
  1242.             PopInline( doc, node );  /* if inline end tag */
  1243.  
  1244.         ReportError(doc, element, node, DISCARDING_UNEXPECTED );
  1245.         FreeNode( doc, node );
  1246.         continue;
  1247.     }
  1248.  
  1249.     if (!(element->tag->model & CM_OPT))
  1250.         ReportError(doc, element, node, MISSING_ENDTAG_FOR);
  1251.  
  1252.     if (element->tag->model & CM_OBJECT)
  1253.     {
  1254.         /* pop inline stack */
  1255.         while ( lexer->istacksize > lexer->istackbase )
  1256.             PopInline( doc, NULL );
  1257.         lexer->istackbase = istackbase;
  1258.     }
  1259.  
  1260.     TrimSpaces( doc, element );
  1261. }
  1262.  
  1263. void ParseInline( TidyDocImpl* doc, Node *element, uint mode )
  1264. {
  1265.     Lexer* lexer = doc->lexer;
  1266.     Node *node, *parent;
  1267.  
  1268.     if (element->tag->model & CM_EMPTY)
  1269.         return;
  1270.  
  1271.     /*
  1272.      ParseInline is used for some block level elements like H1 to H6
  1273.      For such elements we need to insert inline emphasis tags currently
  1274.      on the inline stack. For Inline elements, we normally push them
  1275.      onto the inline stack provided they aren't implicit or OBJECT/APPLET.
  1276.      This test is carried out in PushInline and PopInline, see istack.c
  1277.  
  1278.      InlineDup(...) is not called for elements with a CM_MIXED (inline and
  1279.      block) content model, e.g. <del> or <ins>, otherwise constructs like 
  1280.  
  1281.        <p>111<a name='foo'>222<del>333</del>444</a>555</p>
  1282.        <p>111<span>222<del>333</del>444</span>555</p>
  1283.        <p>111<em>222<del>333</del>444</em>555</p>
  1284.  
  1285.      will get corrupted.
  1286.     */
  1287.     if ((nodeHasCM(element, CM_BLOCK) || nodeIsDT(element)) &&
  1288.         !nodeHasCM(element, CM_MIXED))
  1289.         InlineDup(doc, NULL);
  1290.     else if (nodeHasCM(element, CM_INLINE))
  1291.         PushInline(doc, element);
  1292.  
  1293.     if ( nodeIsNOBR(element) )
  1294.         doc->badLayout |= USING_NOBR;
  1295.     else if ( nodeIsFONT(element) )
  1296.         doc->badLayout |= USING_FONT;
  1297.  
  1298.     /* Inline elements may or may not be within a preformatted element */
  1299.     if (mode != Preformatted)
  1300.         mode = MixedContent;
  1301.  
  1302.     while ((node = GetToken(doc, mode)) != NULL)
  1303.     {
  1304.         /* end tag for current element */
  1305.         if (node->tag == element->tag && node->type == EndTag)
  1306.         {
  1307.             if (element->tag->model & CM_INLINE)
  1308.                 PopInline( doc, node );
  1309.  
  1310.             FreeNode( doc, node );
  1311.  
  1312.             if (!(mode & Preformatted))
  1313.                 TrimSpaces(doc, element);
  1314.  
  1315.             /*
  1316.              if a font element wraps an anchor and nothing else
  1317.              then move the font element inside the anchor since
  1318.              otherwise it won't alter the anchor text color
  1319.             */
  1320.             if ( nodeIsFONT(element) && 
  1321.                  element->content && element->content == element->last )
  1322.             {
  1323.                 Node *child = element->content;
  1324.  
  1325.                 if ( nodeIsA(child) )
  1326.                 {
  1327.                     child->parent = element->parent;
  1328.                     child->next = element->next;
  1329.                     child->prev = element->prev;
  1330.  
  1331.                     if (child->prev)
  1332.                         child->prev->next = child;
  1333.                     else
  1334.                         child->parent->content = child;
  1335.  
  1336.                     if (child->next)
  1337.                         child->next->prev = child;
  1338.                     else
  1339.                         child->parent->last = child;
  1340.  
  1341.                     element->next = NULL;
  1342.                     element->prev = NULL;
  1343.                     element->parent = child;
  1344.                     element->content = child->content;
  1345.                     element->last = child->last;
  1346.                     child->content = child->last = element;
  1347.  
  1348.                     for (child = element->content; child; child = child->next)
  1349.                         child->parent = element;
  1350.                 }
  1351.             }
  1352.  
  1353.             element->closed = yes;
  1354.             TrimSpaces( doc, element );
  1355.             return;
  1356.         }
  1357.  
  1358.         /* <u>...<u>  map 2nd <u> to </u> if 1st is explicit */
  1359.         /* otherwise emphasis nesting is probably unintentional */
  1360.         /* big and small have cumulative effect to leave them alone */
  1361.         if ( node->type == StartTag
  1362.              && node->tag == element->tag
  1363.              && IsPushed( doc, node )
  1364.              && !node->implicit
  1365.              && !element->implicit
  1366.              && node->tag && (node->tag->model & CM_INLINE)
  1367.              && !nodeIsA(node)
  1368.              && !nodeIsFONT(node)
  1369.              && !nodeIsBIG(node)
  1370.              && !nodeIsSMALL(node)
  1371.              && !nodeIsQ(node)
  1372.            )
  1373.         {
  1374.             if (element->content != NULL && node->attributes == NULL)
  1375.             {
  1376.                 ReportWarning(doc, element, node, COERCE_TO_ENDTAG_WARN);
  1377.                 node->type = EndTag;
  1378.                 UngetToken(doc);
  1379.                 continue;
  1380.             }
  1381.  
  1382.             ReportWarning(doc, element, node, NESTED_EMPHASIS);
  1383.         }
  1384.         else if ( IsPushed(doc, node) && node->type == StartTag && 
  1385.                   nodeIsQ(node) )
  1386.         {
  1387.             ReportWarning(doc, element, node, NESTED_QUOTATION);
  1388.         }
  1389.  
  1390.         if ( node->type == TextNode )
  1391.         {
  1392.             /* only called for 1st child */
  1393.             if ( element->content == NULL && !(mode & Preformatted) )
  1394.                 TrimSpaces( doc, element );
  1395.  
  1396.             if ( node->start >= node->end )
  1397.             {
  1398.                 FreeNode( doc, node );
  1399.                 continue;
  1400.             }
  1401.  
  1402.             InsertNodeAtEnd(element, node);
  1403.             continue;
  1404.         }
  1405.  
  1406.         /* mixed content model so allow text */
  1407.         if (InsertMisc(element, node))
  1408.             continue;
  1409.  
  1410.         /* deal with HTML tags */
  1411.         if ( nodeIsHTML(node) )
  1412.         {
  1413.             if ( node->type == StartTag || node->type == StartEndTag )
  1414.             {
  1415.                 ReportError(doc, element, node, DISCARDING_UNEXPECTED );
  1416.                 FreeNode( doc, node );
  1417.                 continue;
  1418.             }
  1419.  
  1420.             /* otherwise infer end of inline element */
  1421.             UngetToken( doc );
  1422.  
  1423.             if (!(mode & Preformatted))
  1424.                 TrimSpaces(doc, element);
  1425.  
  1426.             return;
  1427.         }
  1428.  
  1429.         /* within <dt> or <pre> map <p> to <br> */
  1430.         if ( nodeIsP(node) &&
  1431.              node->type == StartTag &&
  1432.              ( (mode & Preformatted) ||
  1433.                nodeIsDT(element) || 
  1434.                DescendantOf(element, TidyTag_DT )
  1435.              )
  1436.            )
  1437.         {
  1438.             node->tag = LookupTagDef( TidyTag_BR );
  1439.             MemFree(node->element);
  1440.             node->element = tmbstrdup("br");
  1441.             TrimSpaces(doc, element);
  1442.             InsertNodeAtEnd(element, node);
  1443.             continue;
  1444.         }
  1445.  
  1446.         /* ignore unknown and PARAM tags */
  1447.         if ( node->tag == NULL || nodeIsPARAM(node) )
  1448.         {
  1449.             ReportError(doc, element, node, DISCARDING_UNEXPECTED);
  1450.             FreeNode( doc, node );
  1451.             continue;
  1452.         }
  1453.  
  1454.         if ( nodeIsBR(node) && node->type == EndTag )
  1455.             node->type = StartTag;
  1456.  
  1457.         if ( node->type == EndTag )
  1458.         {
  1459.            /* coerce </br> to <br> */
  1460.            if ( nodeIsBR(node) )
  1461.                 node->type = StartTag;
  1462.            else if ( nodeIsP(node) )
  1463.            {
  1464.                /* coerce unmatched </p> to <br><br> */
  1465.                 if ( !DescendantOf(element, TidyTag_P) )
  1466.                 {
  1467.                     CoerceNode(doc, node, TidyTag_BR, no, no);
  1468.                     TrimSpaces( doc, element );
  1469.                     InsertNodeAtEnd( element, node );
  1470.                     node = InferredTag(doc, TidyTag_BR);
  1471.                     continue;
  1472.                 }
  1473.            }
  1474.            else if ( nodeHasCM(node, CM_INLINE)
  1475.                      && !nodeIsA(node)
  1476.                      && !nodeHasCM(node, CM_OBJECT)
  1477.                      && nodeHasCM(element, CM_INLINE) )
  1478.             {
  1479.                 /* allow any inline end tag to end current element */
  1480.                 PopInline( doc, element );
  1481.  
  1482.                 if ( !nodeIsA(element) )
  1483.                 {
  1484.                     if ( nodeIsA(node) && node->tag != element->tag )
  1485.                     {
  1486.                        ReportError(doc, element, node, MISSING_ENDTAG_BEFORE );
  1487.                        UngetToken( doc );
  1488.                     }
  1489.                     else
  1490.                     {
  1491.                         ReportError(doc, element, node, NON_MATCHING_ENDTAG);
  1492.                         FreeNode( doc, node);
  1493.                     }
  1494.  
  1495.                     if (!(mode & Preformatted))
  1496.                         TrimSpaces(doc, element);
  1497.  
  1498.                     return;
  1499.                 }
  1500.  
  1501.                 /* if parent is <a> then discard unexpected inline end tag */
  1502.                 ReportError(doc, element, node, DISCARDING_UNEXPECTED);
  1503.                 FreeNode( doc, node);
  1504.                 continue;
  1505.             }  /* special case </tr> etc. for stuff moved in front of table */
  1506.             else if ( lexer->exiled
  1507.                       && node->tag->model
  1508.                       && (node->tag->model & CM_TABLE) )
  1509.             {
  1510.                 UngetToken( doc );
  1511.                 TrimSpaces(doc, element);
  1512.                 return;
  1513.             }
  1514.         }
  1515.  
  1516.         /* allow any header tag to end current header */
  1517.         if ( nodeHasCM(node, CM_HEADING) && nodeHasCM(element, CM_HEADING) )
  1518.         {
  1519.  
  1520.             if ( node->tag == element->tag )
  1521.             {
  1522.                 ReportError(doc, element, node, NON_MATCHING_ENDTAG );
  1523.                 FreeNode( doc, node);
  1524.             }
  1525.             else
  1526.             {
  1527.                 ReportError(doc, element, node, MISSING_ENDTAG_BEFORE );
  1528.                 UngetToken( doc );
  1529.             }
  1530.  
  1531.             if (!(mode & Preformatted))
  1532.                 TrimSpaces(doc, element);
  1533.  
  1534.             return;
  1535.         }
  1536.  
  1537.         /*
  1538.            an <A> tag to ends any open <A> element
  1539.            but <A href=...> is mapped to </A><A href=...>
  1540.         */
  1541.         /* #427827 - fix by Randy Waki and Bjoern Hoehrmann 23 Aug 00 */
  1542.         /* if (node->tag == doc->tags.tag_a && !node->implicit && IsPushed(doc, node)) */
  1543.         if ( nodeIsA(node) && !node->implicit && 
  1544.              (nodeIsA(element) || DescendantOf(element, TidyTag_A)) )
  1545.         {
  1546.             /* coerce <a> to </a> unless it has some attributes */
  1547.             /* #427827 - fix by Randy Waki and Bjoern Hoehrmann 23 Aug 00 */
  1548.             /* other fixes by Dave Raggett */
  1549.             /* if (node->attributes == NULL) */
  1550.             if (node->type != EndTag && node->attributes == NULL)
  1551.             {
  1552.                 node->type = EndTag;
  1553.                 ReportError(doc, element, node, COERCE_TO_ENDTAG);
  1554.                 /* PopInline( doc, node ); */
  1555.                 UngetToken( doc );
  1556.                 continue;
  1557.             }
  1558.  
  1559.             UngetToken( doc );
  1560.             ReportError(doc, element, node, MISSING_ENDTAG_BEFORE);
  1561.             /* PopInline( doc, element ); */
  1562.  
  1563.             if (!(mode & Preformatted))
  1564.                 TrimSpaces(doc, element);
  1565.  
  1566.             return;
  1567.         }
  1568.  
  1569.         if (element->tag->model & CM_HEADING)
  1570.         {
  1571.             if ( nodeIsCENTER(node) || nodeIsDIV(node) )
  1572.             {
  1573.                 if (node->type != StartTag && node->type != StartEndTag)
  1574.                 {
  1575.                     ReportError(doc, element, node, DISCARDING_UNEXPECTED);
  1576.                     FreeNode( doc, node);
  1577.                     continue;
  1578.                 }
  1579.  
  1580.                 ReportError(doc, element, node, TAG_NOT_ALLOWED_IN);
  1581.  
  1582.                 /* insert center as parent if heading is empty */
  1583.                 if (element->content == NULL)
  1584.                 {
  1585.                     InsertNodeAsParent(element, node);
  1586.                     continue;
  1587.                 }
  1588.  
  1589.                 /* split heading and make center parent of 2nd part */
  1590.                 InsertNodeAfterElement(element, node);
  1591.  
  1592.                 if (!(mode & Preformatted))
  1593.                     TrimSpaces(doc, element);
  1594.  
  1595.                 element = CloneNode( doc, element );
  1596.                 InsertNodeAtEnd(node, element);
  1597.                 continue;
  1598.             }
  1599.  
  1600.             if ( nodeIsHR(node) )
  1601.             {
  1602.                 if ( node->type != StartTag && node->type != StartEndTag )
  1603.                 {
  1604.                     ReportError(doc, element, node, DISCARDING_UNEXPECTED);
  1605.                     FreeNode( doc, node);
  1606.                     continue;
  1607.                 }
  1608.  
  1609.                 ReportError(doc, element, node, TAG_NOT_ALLOWED_IN);
  1610.  
  1611.                 /* insert hr before heading if heading is empty */
  1612.                 if (element->content == NULL)
  1613.                 {
  1614.                     InsertNodeBeforeElement(element, node);
  1615.                     continue;
  1616.                 }
  1617.  
  1618.                 /* split heading and insert hr before 2nd part */
  1619.                 InsertNodeAfterElement(element, node);
  1620.  
  1621.                 if (!(mode & Preformatted))
  1622.                     TrimSpaces(doc, element);
  1623.  
  1624.                 element = CloneNode( doc, element );
  1625.                 InsertNodeAfterElement(node, element);
  1626.                 continue;
  1627.             }
  1628.         }
  1629.  
  1630.         if ( nodeIsDT(element) )
  1631.         {
  1632.             if ( nodeIsHR(node) )
  1633.             {
  1634.                 Node *dd;
  1635.                 if (node->type != StartTag && node->type != StartEndTag)
  1636.                 {
  1637.                     ReportError(doc, element, node, DISCARDING_UNEXPECTED);
  1638.                     FreeNode( doc, node);
  1639.                     continue;
  1640.                 }
  1641.  
  1642.                 ReportError(doc, element, node, TAG_NOT_ALLOWED_IN);
  1643.                 dd = InferredTag(doc, TidyTag_DD);
  1644.  
  1645.                 /* insert hr within dd before dt if dt is empty */
  1646.                 if (element->content == NULL)
  1647.                 {
  1648.                     InsertNodeBeforeElement(element, dd);
  1649.                     InsertNodeAtEnd(dd, node);
  1650.                     continue;
  1651.                 }
  1652.  
  1653.                 /* split dt and insert hr within dd before 2nd part */
  1654.                 InsertNodeAfterElement(element, dd);
  1655.                 InsertNodeAtEnd(dd, node);
  1656.  
  1657.                 if (!(mode & Preformatted))
  1658.                     TrimSpaces(doc, element);
  1659.  
  1660.                 element = CloneNode( doc, element );
  1661.                 InsertNodeAfterElement(dd, element);
  1662.                 continue;
  1663.             }
  1664.         }
  1665.  
  1666.  
  1667.         /* 
  1668.           if this is the end tag for an ancestor element
  1669.           then infer end tag for this element
  1670.         */
  1671.         if (node->type == EndTag)
  1672.         {
  1673.             for (parent = element->parent;
  1674.                     parent != NULL; parent = parent->parent)
  1675.             {
  1676.                 if (node->tag == parent->tag)
  1677.                 {
  1678.                     if (!(element->tag->model & CM_OPT) && !element->implicit)
  1679.                         ReportError(doc, element, node, MISSING_ENDTAG_BEFORE);
  1680.  
  1681.                     PopInline( doc, element );
  1682.                     UngetToken( doc );
  1683.  
  1684.                     if (!(mode & Preformatted))
  1685.                         TrimSpaces(doc, element);
  1686.  
  1687.                     return;
  1688.                 }
  1689.             }
  1690.         }
  1691.  
  1692.         /* block level tags end this element */
  1693.         if (!(node->tag->model & CM_INLINE) &&
  1694.             !(element->tag->model & CM_MIXED))
  1695.         {
  1696.             if (node->type != StartTag && node->type != StartEndTag)
  1697.             {
  1698.                 ReportError(doc, element, node, DISCARDING_UNEXPECTED);
  1699.                 FreeNode( doc, node);
  1700.                 continue;
  1701.             }
  1702.  
  1703.             if (!(element->tag->model & CM_OPT))
  1704.                 ReportError(doc, element, node, MISSING_ENDTAG_BEFORE);
  1705.  
  1706.             if (node->tag->model & CM_HEAD && !(node->tag->model & CM_BLOCK))
  1707.             {
  1708.                 MoveToHead(doc, element, node);
  1709.                 continue;
  1710.             }
  1711.  
  1712.             /*
  1713.                prevent anchors from propagating into block tags
  1714.                except for headings h1 to h6
  1715.             */
  1716.             if ( nodeIsA(element) )
  1717.             {
  1718.                 if (node->tag && !(node->tag->model & CM_HEADING))
  1719.                     PopInline( doc, element );
  1720.                 else if (!(element->content))
  1721.                 {
  1722.                     DiscardElement( doc, element );
  1723.                     UngetToken( doc );
  1724.                     return;
  1725.                 }
  1726.             }
  1727.  
  1728.             UngetToken( doc );
  1729.  
  1730.             if (!(mode & Preformatted))
  1731.                 TrimSpaces(doc, element);
  1732.  
  1733.             return;
  1734.         }
  1735.  
  1736.         /* parse inline element */
  1737.         if (node->type == StartTag || node->type == StartEndTag)
  1738.         {
  1739.             if (node->implicit)
  1740.                 ReportError(doc, element, node, INSERTING_TAG);
  1741.  
  1742.             /* trim white space before <br> */
  1743.             if ( nodeIsBR(node) )
  1744.                 TrimSpaces(doc, element);
  1745.             
  1746.             InsertNodeAtEnd(element, node);
  1747.             ParseTag(doc, node, mode);
  1748.             continue;
  1749.         }
  1750.  
  1751.         /* discard unexpected tags */
  1752.         ReportError(doc, element, node, DISCARDING_UNEXPECTED);
  1753.         FreeNode( doc, node );
  1754.         continue;
  1755.     }
  1756.  
  1757.     if (!(element->tag->model & CM_OPT))
  1758.         ReportError(doc, element, node, MISSING_ENDTAG_FOR);
  1759.  
  1760. }
  1761.  
  1762. void ParseEmpty(TidyDocImpl* doc, Node *element, uint mode)
  1763. {
  1764.     Lexer* lexer = doc->lexer;
  1765.     if ( lexer->isvoyager )
  1766.     {
  1767.         Node *node = GetToken( doc, mode);
  1768.         if ( node )
  1769.         {
  1770.             if ( !(node->type == EndTag && node->tag == element->tag) )
  1771.             {
  1772.                 ReportError(doc, element, node, ELEMENT_NOT_EMPTY);
  1773.                 UngetToken( doc );
  1774.             }
  1775.             else
  1776.             {
  1777.                 FreeNode( doc, node );
  1778.             }
  1779.         }
  1780.     }
  1781. }
  1782.  
  1783. void ParseDefList(TidyDocImpl* doc, Node *list, uint mode)
  1784. {
  1785.     Lexer* lexer = doc->lexer;
  1786.     Node *node, *parent;
  1787.  
  1788.     if (list->tag->model & CM_EMPTY)
  1789.         return;
  1790.  
  1791.     lexer->insert = NULL;  /* defer implicit inline start tags */
  1792.  
  1793.     while ((node = GetToken( doc, IgnoreWhitespace)) != NULL)
  1794.     {
  1795.         if (node->tag == list->tag && node->type == EndTag)
  1796.         {
  1797.             FreeNode( doc, node);
  1798.             list->closed = yes;
  1799.             return;
  1800.         }
  1801.  
  1802.         /* deal with comments etc. */
  1803.         if (InsertMisc(list, node))
  1804.             continue;
  1805.  
  1806.         if (node->type == TextNode)
  1807.         {
  1808.             UngetToken( doc );
  1809.             node = InferredTag(doc, TidyTag_DT);
  1810.             ReportError(doc, list, node, MISSING_STARTTAG);
  1811.         }
  1812.  
  1813.         if (node->tag == NULL)
  1814.         {
  1815.             ReportError(doc, list, node, DISCARDING_UNEXPECTED);
  1816.             FreeNode( doc, node);
  1817.             continue;
  1818.         }
  1819.  
  1820.         /* 
  1821.           if this is the end tag for an ancestor element
  1822.           then infer end tag for this element
  1823.         */
  1824.         if (node->type == EndTag)
  1825.         {
  1826.             if ( nodeIsFORM(node) )
  1827.             {
  1828.                 BadForm( doc );
  1829.                 ReportError(doc, list, node, DISCARDING_UNEXPECTED);
  1830.                 FreeNode( doc, node );
  1831.                 continue;
  1832.             }
  1833.  
  1834.             for (parent = list->parent;
  1835.                     parent != NULL; parent = parent->parent)
  1836.             {
  1837.                 if (node->tag == parent->tag)
  1838.                 {
  1839.                     ReportError(doc, list, node, MISSING_ENDTAG_BEFORE);
  1840.  
  1841.                     UngetToken( doc );
  1842.                     return;
  1843.                 }
  1844.             }
  1845.         }
  1846.  
  1847.         /* center in a dt or a dl breaks the dl list in two */
  1848.         if ( nodeIsCENTER(node) )
  1849.         {
  1850.             if (list->content)
  1851.                 InsertNodeAfterElement(list, node);
  1852.             else /* trim empty dl list */
  1853.             {
  1854.                 InsertNodeBeforeElement(list, node);
  1855.  
  1856. /* #540296 tidy dumps with empty definition list */
  1857. #if 0
  1858.                 DiscardElement(list);
  1859. #endif
  1860.             }
  1861.  
  1862.             /* #426885 - fix by Glenn Carroll 19 Apr 00, and
  1863.                          Gary Dechaines 11 Aug 00 */
  1864.             /* ParseTag can destroy node, if it finds that
  1865.              * this <center> is followed immediately by </center>.
  1866.              * It's awkward but necessary to determine if this
  1867.              * has happened.
  1868.              */
  1869.             parent = node->parent;
  1870.  
  1871.             /* and parse contents of center */
  1872.             lexer->excludeBlocks = no;
  1873.             ParseTag( doc, node, mode);
  1874.             lexer->excludeBlocks = yes;
  1875.  
  1876.             /* now create a new dl element,
  1877.              * unless node has been blown away because the
  1878.              * center was empty, as above.
  1879.              */
  1880.             if (parent->last == node)
  1881.             {
  1882.                 list = InferredTag(doc, TidyTag_DL);
  1883.                 InsertNodeAfterElement(node, list);
  1884.             }
  1885.             continue;
  1886.         }
  1887.  
  1888.         if ( !(nodeIsDT(node) || nodeIsDD(node)) )
  1889.         {
  1890.             UngetToken( doc );
  1891.  
  1892.             if (!(node->tag->model & (CM_BLOCK | CM_INLINE)))
  1893.             {
  1894.                 ReportError(doc, list, node, TAG_NOT_ALLOWED_IN);
  1895.                 return;
  1896.             }
  1897.  
  1898.             /* if DD appeared directly in BODY then exclude blocks */
  1899.             if (!(node->tag->model & CM_INLINE) && lexer->excludeBlocks)
  1900.                 return;
  1901.  
  1902.             node = InferredTag(doc, TidyTag_DD);
  1903.             ReportError(doc, list, node, MISSING_STARTTAG);
  1904.         }
  1905.  
  1906.         if (node->type == EndTag)
  1907.         {
  1908.             ReportError(doc, list, node, DISCARDING_UNEXPECTED);
  1909.             FreeNode( doc, node);
  1910.             continue;
  1911.         }
  1912.         
  1913.         /* node should be <DT> or <DD>*/
  1914.         InsertNodeAtEnd(list, node);
  1915.         ParseTag( doc, node, IgnoreWhitespace);
  1916.     }
  1917.  
  1918.     ReportError(doc, list, node, MISSING_ENDTAG_FOR);
  1919. }
  1920.  
  1921. void ParseList(TidyDocImpl* doc, Node *list, uint mode)
  1922. {
  1923. #pragma unused(mode)
  1924.  
  1925.     Lexer* lexer = doc->lexer;
  1926.     Node *node, *parent;
  1927.  
  1928.     if (list->tag->model & CM_EMPTY)
  1929.         return;
  1930.  
  1931.     lexer->insert = NULL;  /* defer implicit inline start tags */
  1932.  
  1933.     while ((node = GetToken( doc, IgnoreWhitespace)) != NULL)
  1934.     {
  1935.         if (node->tag == list->tag && node->type == EndTag)
  1936.         {
  1937.             FreeNode( doc, node);
  1938.             list->closed = yes;
  1939.             return;
  1940.         }
  1941.  
  1942.         /* deal with comments etc. */
  1943.         if (InsertMisc(list, node))
  1944.             continue;
  1945.  
  1946.         if (node->type != TextNode && node->tag == NULL)
  1947.         {
  1948.             ReportError(doc, list, node, DISCARDING_UNEXPECTED);
  1949.             FreeNode( doc, node);
  1950.             continue;
  1951.         }
  1952.  
  1953.         /* 
  1954.           if this is the end tag for an ancestor element
  1955.           then infer end tag for this element
  1956.         */
  1957.         if (node->type == EndTag)
  1958.         {
  1959.             if ( nodeIsFORM(node) )
  1960.             {
  1961.                 BadForm( doc );
  1962.                 ReportError(doc, list, node, DISCARDING_UNEXPECTED);
  1963.                 FreeNode( doc, node );
  1964.                 continue;
  1965.             }
  1966.  
  1967.             if (node->tag && node->tag->model & CM_INLINE)
  1968.             {
  1969.                 ReportError(doc, list, node, DISCARDING_UNEXPECTED);
  1970.                 PopInline( doc, node );
  1971.                 FreeNode( doc, node);
  1972.                 continue;
  1973.             }
  1974.  
  1975.             for ( parent = list->parent;
  1976.                   parent != NULL; parent = parent->parent )
  1977.             {
  1978.                 if (node->tag == parent->tag)
  1979.                 {
  1980.                     ReportError(doc, list, node, MISSING_ENDTAG_BEFORE);
  1981.                     UngetToken( doc );
  1982.                     return;
  1983.                 }
  1984.             }
  1985.  
  1986.             ReportError(doc, list, node, DISCARDING_UNEXPECTED);
  1987.             FreeNode( doc, node);
  1988.             continue;
  1989.         }
  1990.  
  1991.         if ( !nodeIsLI(node) )
  1992.         {
  1993.             UngetToken( doc );
  1994.  
  1995.             if (node->tag && (node->tag->model & CM_BLOCK) && lexer->excludeBlocks)
  1996.             {
  1997.                 ReportError(doc, list, node, MISSING_ENDTAG_BEFORE);
  1998.                 return;
  1999.             }
  2000.  
  2001.             node = InferredTag(doc, TidyTag_LI);
  2002.             AddAttribute( doc, node, "style", "list-style: none" );
  2003.             ReportError(doc, list, node, MISSING_STARTTAG );
  2004.         }
  2005.  
  2006.         /* node should be <LI> */
  2007.         InsertNodeAtEnd(list,node);
  2008.         ParseTag( doc, node, IgnoreWhitespace);
  2009.     }
  2010.  
  2011.     ReportError(doc, list, node, MISSING_ENDTAG_FOR);
  2012. }
  2013.  
  2014. /*
  2015.  unexpected content in table row is moved to just before
  2016.  the table in accordance with Netscape and IE. This code
  2017.  assumes that node hasn't been inserted into the row.
  2018. */
  2019. static void MoveBeforeTable( TidyDocImpl* doc, Node *row, Node *node )
  2020. {
  2021. #pragma unused(doc)
  2022.  
  2023.     Node *table;
  2024.  
  2025.     /* first find the table element */
  2026.     for (table = row->parent; table; table = table->parent)
  2027.     {
  2028.         if ( nodeIsTABLE(table) )
  2029.         {
  2030.             if (table->parent->content == table)
  2031.                 table->parent->content = node;
  2032.  
  2033.             node->prev = table->prev;
  2034.             node->next = table;
  2035.             table->prev = node;
  2036.             node->parent = table->parent;
  2037.         
  2038.             if (node->prev)
  2039.                 node->prev->next = node;
  2040.  
  2041.             break;
  2042.         }
  2043.     }
  2044. }
  2045.  
  2046. /*
  2047.  if a table row is empty then insert an empty cell
  2048.  this practice is consistent with browser behavior
  2049.  and avoids potential problems with row spanning cells
  2050. */
  2051. static void FixEmptyRow(TidyDocImpl* doc, Node *row)
  2052. {
  2053.     Node *cell;
  2054.  
  2055.     if (row->content == NULL)
  2056.     {
  2057.         cell = InferredTag(doc, TidyTag_TD);
  2058.         InsertNodeAtEnd(row, cell);
  2059.         ReportError(doc, row, cell, MISSING_STARTTAG);
  2060.     }
  2061. }
  2062.  
  2063. void ParseRow(TidyDocImpl* doc, Node *row, uint mode)
  2064. {
  2065. #pragma unused(mode)
  2066.  
  2067.     Lexer* lexer = doc->lexer;
  2068.     Node *node;
  2069.     Bool exclude_state;
  2070.  
  2071.     if (row->tag->model & CM_EMPTY)
  2072.         return;
  2073.  
  2074.     while ((node = GetToken(doc, IgnoreWhitespace)) != NULL)
  2075.     {
  2076.         if (node->tag == row->tag)
  2077.         {
  2078.             if (node->type == EndTag)
  2079.             {
  2080.                 FreeNode( doc, node);
  2081.                 row->closed = yes;
  2082.                 FixEmptyRow( doc, row);
  2083.                 return;
  2084.             }
  2085.  
  2086.             /* New row start implies end of current row */
  2087.             UngetToken( doc );
  2088.             FixEmptyRow( doc, row);
  2089.             return;
  2090.         }
  2091.  
  2092.         /* 
  2093.           if this is the end tag for an ancestor element
  2094.           then infer end tag for this element
  2095.         */
  2096.         if ( node->type == EndTag )
  2097.         {
  2098.             if ( DescendantOf(row, TagId(node)) )
  2099.             {
  2100.                 UngetToken( doc );
  2101.                 return;
  2102.             }
  2103.  
  2104.             if ( nodeIsFORM(node) || nodeHasCM(node, CM_BLOCK|CM_INLINE) )
  2105.             {
  2106.                 if ( nodeIsFORM(node) )
  2107.                     BadForm( doc );
  2108.  
  2109.                 ReportError(doc, row, node, DISCARDING_UNEXPECTED);
  2110.                 FreeNode( doc, node);
  2111.                 continue;
  2112.             }
  2113.  
  2114.             if ( nodeIsTD(node) || nodeIsTH(node) )
  2115.             {
  2116.                 ReportError(doc, row, node, DISCARDING_UNEXPECTED);
  2117.                 FreeNode( doc, node);
  2118.                 continue;
  2119.             }
  2120.         }
  2121.  
  2122.         /* deal with comments etc. */
  2123.         if (InsertMisc(row, node))
  2124.             continue;
  2125.  
  2126.         /* discard unknown tags */
  2127.         if (node->tag == NULL && node->type != TextNode)
  2128.         {
  2129.             ReportError(doc, row, node, DISCARDING_UNEXPECTED);
  2130.             FreeNode( doc, node);
  2131.             continue;
  2132.         }
  2133.  
  2134.         /* discard unexpected <table> element */
  2135.         if ( nodeIsTABLE(node) )
  2136.         {
  2137.             ReportError(doc, row, node, DISCARDING_UNEXPECTED);
  2138.             FreeNode( doc, node);
  2139.             continue;
  2140.         }
  2141.  
  2142.         /* THEAD, TFOOT or TBODY */
  2143.         if ( nodeHasCM(node, CM_ROWGRP) )
  2144.         {
  2145.             UngetToken( doc );
  2146.             return;
  2147.         }
  2148.  
  2149.         if (node->type == EndTag)
  2150.         {
  2151.             ReportError(doc, row, node, DISCARDING_UNEXPECTED);
  2152.             FreeNode( doc, node);
  2153.             continue;
  2154.         }
  2155.  
  2156.         /*
  2157.           if text or inline or block move before table
  2158.           if head content move to head
  2159.         */
  2160.  
  2161.         if (node->type != EndTag)
  2162.         {
  2163.             if ( nodeIsFORM(node) )
  2164.             {
  2165.                 UngetToken( doc );
  2166.                 node = InferredTag(doc, TidyTag_TD);
  2167.                 ReportError(doc, row, node, MISSING_STARTTAG);
  2168.             }
  2169.             else if ( nodeIsText(node)
  2170.                       || nodeHasCM(node, CM_BLOCK | CM_INLINE) )
  2171.             {
  2172.                 MoveBeforeTable( doc, row, node );
  2173.                 ReportError(doc, row, node, TAG_NOT_ALLOWED_IN);
  2174.                 lexer->exiled = yes;
  2175.  
  2176.                 if (node->type != TextNode)
  2177.                     ParseTag( doc, node, IgnoreWhitespace);
  2178.  
  2179.                 lexer->exiled = no;
  2180.                 continue;
  2181.             }
  2182.             else if (node->tag->model & CM_HEAD)
  2183.             {
  2184.                 ReportError(doc, row, node, TAG_NOT_ALLOWED_IN);
  2185.                 MoveToHead( doc, row, node);
  2186.                 continue;
  2187.             }
  2188.         }
  2189.  
  2190.         if ( !(nodeIsTD(node) || nodeIsTH(node)) )
  2191.         {
  2192.             ReportError(doc, row, node, TAG_NOT_ALLOWED_IN);
  2193.             FreeNode( doc, node);
  2194.             continue;
  2195.         }
  2196.         
  2197.         /* node should be <TD> or <TH> */
  2198.         InsertNodeAtEnd(row, node);
  2199.         exclude_state = lexer->excludeBlocks;
  2200.         lexer->excludeBlocks = no;
  2201.         ParseTag( doc, node, IgnoreWhitespace);
  2202.         lexer->excludeBlocks = exclude_state;
  2203.  
  2204.         /* pop inline stack */
  2205.  
  2206.         while ( lexer->istacksize > lexer->istackbase )
  2207.             PopInline( doc, NULL );
  2208.     }
  2209.  
  2210. }
  2211.  
  2212. void ParseRowGroup(TidyDocImpl* doc, Node *rowgroup, uint mode)
  2213. {
  2214. #pragma unused(mode)
  2215.  
  2216.     Lexer* lexer = doc->lexer;
  2217.     Node *node, *parent;
  2218.  
  2219.     if (rowgroup->tag->model & CM_EMPTY)
  2220.         return;
  2221.  
  2222.     while ((node = GetToken(doc, IgnoreWhitespace)) != NULL)
  2223.     {
  2224.         if (node->tag == rowgroup->tag)
  2225.         {
  2226.             if (node->type == EndTag)
  2227.             {
  2228.                 rowgroup->closed = yes;
  2229.                 FreeNode( doc, node);
  2230.                 return;
  2231.             }
  2232.  
  2233.             UngetToken( doc );
  2234.             return;
  2235.         }
  2236.  
  2237.         /* if </table> infer end tag */
  2238.         if ( nodeIsTABLE(node) && node->type == EndTag )
  2239.         {
  2240.             UngetToken( doc );
  2241.             return;
  2242.         }
  2243.  
  2244.         /* deal with comments etc. */
  2245.         if (InsertMisc(rowgroup, node))
  2246.             continue;
  2247.  
  2248.         /* discard unknown tags */
  2249.         if (node->tag == NULL && node->type != TextNode)
  2250.         {
  2251.             ReportError(doc, rowgroup, node, DISCARDING_UNEXPECTED);
  2252.             FreeNode( doc, node);
  2253.             continue;
  2254.         }
  2255.  
  2256.         /*
  2257.           if TD or TH then infer <TR>
  2258.           if text or inline or block move before table
  2259.           if head content move to head
  2260.         */
  2261.  
  2262.         if (node->type != EndTag)
  2263.         {
  2264.             if ( nodeIsTD(node) || nodeIsTH(node) )
  2265.             {
  2266.                 UngetToken( doc );
  2267.                 node = InferredTag(doc, TidyTag_TR);
  2268.                 ReportError(doc, rowgroup, node, MISSING_STARTTAG);
  2269.             }
  2270.             else if ( nodeIsText(node)
  2271.                       || nodeHasCM(node, CM_BLOCK|CM_INLINE) )
  2272.             {
  2273.                 MoveBeforeTable( doc, rowgroup, node );
  2274.                 ReportError(doc, rowgroup, node, TAG_NOT_ALLOWED_IN);
  2275.                 lexer->exiled = yes;
  2276.  
  2277.                 if (node->type != TextNode)
  2278.                     ParseTag(doc, node, IgnoreWhitespace);
  2279.  
  2280.                 lexer->exiled = no;
  2281.                 continue;
  2282.             }
  2283.             else if (node->tag->model & CM_HEAD)
  2284.             {
  2285.                 ReportError(doc, rowgroup, node, TAG_NOT_ALLOWED_IN);
  2286.                 MoveToHead(doc, rowgroup, node);
  2287.                 continue;
  2288.             }
  2289.         }
  2290.  
  2291.         /* 
  2292.           if this is the end tag for ancestor element
  2293.           then infer end tag for this element
  2294.         */
  2295.         if (node->type == EndTag)
  2296.         {
  2297.             if ( nodeIsFORM(node) || nodeHasCM(node, CM_BLOCK|CM_INLINE) )
  2298.             {
  2299.                 if ( nodeIsFORM(node) )
  2300.                     BadForm( doc );
  2301.  
  2302.                 ReportError(doc, rowgroup, node, DISCARDING_UNEXPECTED);
  2303.                 FreeNode( doc, node);
  2304.                 continue;
  2305.             }
  2306.  
  2307.             if ( nodeIsTR(node) || nodeIsTD(node) || nodeIsTH(node) )
  2308.             {
  2309.                 ReportError(doc, rowgroup, node, DISCARDING_UNEXPECTED);
  2310.                 FreeNode( doc, node);
  2311.                 continue;
  2312.             }
  2313.  
  2314.             for ( parent = rowgroup->parent;
  2315.                   parent != NULL;
  2316.                   parent = parent->parent )
  2317.             {
  2318.                 if (node->tag == parent->tag)
  2319.                 {
  2320.                     UngetToken( doc );
  2321.                     return;
  2322.                 }
  2323.             }
  2324.         }
  2325.  
  2326.         /*
  2327.           if THEAD, TFOOT or TBODY then implied end tag
  2328.  
  2329.         */
  2330.         if (node->tag->model & CM_ROWGRP)
  2331.         {
  2332.             if (node->type != EndTag)
  2333.                 UngetToken( doc );
  2334.  
  2335.             return;
  2336.         }
  2337.  
  2338.         if (node->type == EndTag)
  2339.         {
  2340.             ReportError(doc, rowgroup, node, DISCARDING_UNEXPECTED);
  2341.             FreeNode( doc, node);
  2342.             continue;
  2343.         }
  2344.         
  2345.         if ( !nodeIsTR(node) )
  2346.         {
  2347.             node = InferredTag(doc, TidyTag_TR);
  2348.             ReportError(doc, rowgroup, node, MISSING_STARTTAG);
  2349.             UngetToken( doc );
  2350.         }
  2351.  
  2352.        /* node should be <TR> */
  2353.         InsertNodeAtEnd(rowgroup, node);
  2354.         ParseTag(doc, node, IgnoreWhitespace);
  2355.     }
  2356.  
  2357. }
  2358.  
  2359. void ParseColGroup(TidyDocImpl* doc, Node *colgroup, uint mode)
  2360. {
  2361. #pragma unused(mode)
  2362.  
  2363.     Node *node, *parent;
  2364.  
  2365.     if (colgroup->tag->model & CM_EMPTY)
  2366.         return;
  2367.  
  2368.     while ((node = GetToken(doc, IgnoreWhitespace)) != NULL)
  2369.     {
  2370.         if (node->tag == colgroup->tag && node->type == EndTag)
  2371.         {
  2372.             FreeNode( doc, node);
  2373.             colgroup->closed = yes;
  2374.             return;
  2375.         }
  2376.  
  2377.         /* 
  2378.           if this is the end tag for an ancestor element
  2379.           then infer end tag for this element
  2380.         */
  2381.         if (node->type == EndTag)
  2382.         {
  2383.             if ( nodeIsFORM(node) )
  2384.             {
  2385.                 BadForm( doc );
  2386.                 ReportError(doc, colgroup, node, DISCARDING_UNEXPECTED);
  2387.                 FreeNode( doc, node);
  2388.                 continue;
  2389.             }
  2390.  
  2391.             for ( parent = colgroup->parent;
  2392.                   parent != NULL;
  2393.                   parent = parent->parent )
  2394.             {
  2395.                 if (node->tag == parent->tag)
  2396.                 {
  2397.                     UngetToken( doc );
  2398.                     return;
  2399.                 }
  2400.             }
  2401.         }
  2402.  
  2403.         if (node->type == TextNode)
  2404.         {
  2405.             UngetToken( doc );
  2406.             return;
  2407.         }
  2408.  
  2409.         /* deal with comments etc. */
  2410.         if (InsertMisc(colgroup, node))
  2411.             continue;
  2412.  
  2413.         /* discard unknown tags */
  2414.         if (node->tag == NULL)
  2415.         {
  2416.             ReportError(doc, colgroup, node, DISCARDING_UNEXPECTED);
  2417.             FreeNode( doc, node);
  2418.             continue;
  2419.         }
  2420.  
  2421.         if ( !nodeIsCOL(node) )
  2422.         {
  2423.             UngetToken( doc );
  2424.             return;
  2425.         }
  2426.  
  2427.         if (node->type == EndTag)
  2428.         {
  2429.             ReportError(doc, colgroup, node, DISCARDING_UNEXPECTED);
  2430.             FreeNode( doc, node);
  2431.             continue;
  2432.         }
  2433.         
  2434.         /* node should be <COL> */
  2435.         InsertNodeAtEnd(colgroup, node);
  2436.         ParseTag(doc, node, IgnoreWhitespace);
  2437.     }
  2438. }
  2439.  
  2440. void ParseTableTag(TidyDocImpl* doc, Node *table, uint mode)
  2441. {
  2442. #pragma unused(mode)
  2443.  
  2444.     Lexer* lexer = doc->lexer;
  2445.     Node *node, *parent;
  2446.     uint istackbase;
  2447.  
  2448.     DeferDup( doc );
  2449.     istackbase = lexer->istackbase;
  2450.     lexer->istackbase = lexer->istacksize;
  2451.     
  2452.     while ((node = GetToken(doc, IgnoreWhitespace)) != NULL)
  2453.     {
  2454.         if (node->tag == table->tag && node->type == EndTag)
  2455.         {
  2456.             FreeNode( doc, node);
  2457.             lexer->istackbase = istackbase;
  2458.             table->closed = yes;
  2459.             return;
  2460.         }
  2461.  
  2462.         /* deal with comments etc. */
  2463.         if (InsertMisc(table, node))
  2464.             continue;
  2465.  
  2466.         /* discard unknown tags */
  2467.         if (node->tag == NULL && node->type != TextNode)
  2468.         {
  2469.             ReportError(doc, table, node, DISCARDING_UNEXPECTED);
  2470.             FreeNode( doc, node);
  2471.             continue;
  2472.         }
  2473.  
  2474.         /* if TD or TH or text or inline or block then infer <TR> */
  2475.  
  2476.         if (node->type != EndTag)
  2477.         {
  2478.             if ( nodeIsTD(node) || nodeIsTH(node) || nodeIsTABLE(node) )
  2479.             {
  2480.                 UngetToken( doc );
  2481.                 node = InferredTag(doc, TidyTag_TR);
  2482.                 ReportError(doc, table, node, MISSING_STARTTAG);
  2483.             }
  2484.             else if ( nodeIsText(node) ||nodeHasCM(node,CM_BLOCK|CM_INLINE) )
  2485.             {
  2486.                 InsertNodeBeforeElement(table, node);
  2487.                 ReportError(doc, table, node, TAG_NOT_ALLOWED_IN);
  2488.                 lexer->exiled = yes;
  2489.  
  2490.                 if (node->type != TextNode) 
  2491.                     ParseTag(doc, node, IgnoreWhitespace);
  2492.  
  2493.                 lexer->exiled = no;
  2494.                 continue;
  2495.             }
  2496.             else if (node->tag->model & CM_HEAD)
  2497.             {
  2498.                 MoveToHead(doc, table, node);
  2499.                 continue;
  2500.             }
  2501.         }
  2502.  
  2503.         /* 
  2504.           if this is the end tag for an ancestor element
  2505.           then infer end tag for this element
  2506.         */
  2507.         if (node->type == EndTag)
  2508.         {
  2509.             if ( nodeIsFORM(node) )
  2510.             {
  2511.                 BadForm( doc );
  2512.                 ReportError(doc, table, node, DISCARDING_UNEXPECTED);
  2513.                 FreeNode( doc, node);
  2514.                 continue;
  2515.             }
  2516.  
  2517.             /* best to discard unexpected block/inline end tags */
  2518.             if ( nodeHasCM(node, CM_TABLE|CM_ROW) ||
  2519.                  nodeHasCM(node, CM_BLOCK|CM_INLINE) )
  2520.             {
  2521.                 ReportError(doc, table, node, DISCARDING_UNEXPECTED);
  2522.                 FreeNode( doc, node);
  2523.                 continue;
  2524.             }
  2525.  
  2526.             for ( parent = table->parent;
  2527.                   parent != NULL;
  2528.                   parent = parent->parent )
  2529.             {
  2530.                 if (node->tag == parent->tag)
  2531.                 {
  2532.                     ReportError(doc, table, node, MISSING_ENDTAG_BEFORE );
  2533.                     UngetToken( doc );
  2534.                     lexer->istackbase = istackbase;
  2535.                     return;
  2536.                 }
  2537.             }
  2538.         }
  2539.  
  2540.         if (!(node->tag->model & CM_TABLE))
  2541.         {
  2542.             UngetToken( doc );
  2543.             ReportError(doc, table, node, TAG_NOT_ALLOWED_IN);
  2544.             lexer->istackbase = istackbase;
  2545.             return;
  2546.         }
  2547.  
  2548.         if (node->type == StartTag || node->type == StartEndTag)
  2549.         {
  2550.             InsertNodeAtEnd(table, node);
  2551.             ParseTag(doc, node, IgnoreWhitespace);
  2552.             continue;
  2553.         }
  2554.  
  2555.         /* discard unexpected text nodes and end tags */
  2556.         ReportError(doc, table, node, DISCARDING_UNEXPECTED);
  2557.         FreeNode( doc, node);
  2558.     }
  2559.  
  2560.     ReportError(doc, table, node, MISSING_ENDTAG_FOR);
  2561.     lexer->istackbase = istackbase;
  2562. }
  2563.  
  2564. /* acceptable content for pre elements */
  2565. Bool PreContent( TidyDocImpl* doc, Node* node )
  2566. {
  2567. #pragma unused(doc)
  2568.  
  2569.     /* p is coerced to br's, Text OK too */
  2570.     if ( nodeIsP(node) || nodeIsText(node) )
  2571.         return yes;
  2572.  
  2573.     if ( node->tag == NULL ||
  2574.          nodeIsPARAM(node) ||
  2575.          !nodeHasCM(node, CM_INLINE|CM_NEW) )
  2576.         return no;
  2577.  
  2578.     return yes;
  2579. }
  2580.  
  2581. void ParsePre( TidyDocImpl* doc, Node *pre, uint mode )
  2582. {
  2583. #pragma unused(mode)
  2584.  
  2585.     Node *node;
  2586.  
  2587.     if (pre->tag->model & CM_EMPTY)
  2588.         return;
  2589.  
  2590.     InlineDup( doc, NULL ); /* tell lexer to insert inlines if needed */
  2591.  
  2592.     while ((node = GetToken(doc, Preformatted)) != NULL)
  2593.     {
  2594.         if ( node->type == EndTag && 
  2595.              (node->tag == pre->tag || DescendantOf(pre, TagId(node))) )
  2596.         {
  2597.             if (nodeIsBODY(node) || nodeIsHTML(node))
  2598.             {
  2599.                 ReportError(doc, pre, node, DISCARDING_UNEXPECTED);
  2600.                 FreeNode(doc, node);
  2601.                 continue;
  2602.             }
  2603.             if (node->tag == pre->tag)
  2604.             {
  2605.                 FreeNode(doc, node);
  2606.             }
  2607.             else
  2608.             {
  2609.                 ReportError(doc, pre, node, MISSING_ENDTAG_BEFORE );
  2610.                 UngetToken( doc );
  2611.             }
  2612.             pre->closed = yes;
  2613.             TrimSpaces(doc, pre);
  2614.             return;
  2615.         }
  2616.  
  2617.         if (node->type == TextNode)
  2618.         {
  2619.             InsertNodeAtEnd(pre, node);
  2620.             continue;
  2621.         }
  2622.  
  2623.         /* deal with comments etc. */
  2624.         if (InsertMisc(pre, node))
  2625.             continue;
  2626.  
  2627.         if (node->tag == NULL)
  2628.         {
  2629.             ReportError(doc, pre, node, DISCARDING_UNEXPECTED);
  2630.             FreeNode(doc, node);
  2631.             continue;
  2632.         }
  2633.  
  2634.         /* strip unexpected tags */
  2635.         if ( !PreContent(doc, node) )
  2636.         {
  2637.             Node *newnode;
  2638.  
  2639.             /* fix for http://tidy.sf.net/bug/772205 */
  2640.             if (node->type == EndTag)
  2641.             {
  2642.                ReportError(doc, pre, node, DISCARDING_UNEXPECTED);
  2643.                FreeNode(doc, node);
  2644.                continue;
  2645.             }
  2646.             /*
  2647.               This is basically what Tidy 04 August 2000 did and far more accurate
  2648.               with respect to browser behaivour than the code commented out above.
  2649.               Tidy could try to propagate the <pre> into each disallowed child where
  2650.               <pre> is allowed in order to replicate some browsers behaivour, but
  2651.               there are a lot of exceptions, e.g. Internet Explorer does not propagate
  2652.               <pre> into table cells while Mozilla does. Opera 6 never propagates
  2653.               <pre> into blocklevel elements while Opera 7 behaves much like Mozilla.
  2654.  
  2655.               Tidy behaves thus mostly like Opera 6 except for nested <pre> elements
  2656.               which are handled like Mozilla takes them (Opera6 closes all <pre> after
  2657.               the first </pre>).
  2658.  
  2659.               There are similar issues like replacing <p> in <pre> with <br>, for
  2660.               example
  2661.  
  2662.                 <pre>...<p>...</pre>                 (Input)
  2663.                 <pre>...<br>...</pre>                (Tidy)
  2664.                 <pre>...<br>...</pre>                (Opera 7 and Internet Explorer)
  2665.                 <pre>...<br><br>...</pre>            (Opera 6 and Mozilla)
  2666.  
  2667.                 <pre>...<p>...</p>...</pre>          (Input)
  2668.                 <pre>...<br>......</pre>             (Tidy, BUG!)
  2669.                 <pre>...<br>...<br>...</pre>         (Internet Explorer)
  2670.                 <pre>...<br><br>...<br><br>...</pre> (Mozilla, Opera 6)
  2671.                 <pre>...<br>...<br><br>...</pre>     (Opera 7)
  2672.                 
  2673.               or something similar, they could also be closing the <pre> and propagate
  2674.               the <pre> into the newly opened <p>.
  2675.  
  2676.               Todo: IMG, OBJECT, APPLET, BIG, SMALL, SUB, SUP, FONT, and BASEFONT are
  2677.               dissallowed in <pre>, Tidy neither detects this nor does it perform any
  2678.               cleanup operation. Tidy should at least issue a warning if it encounters
  2679.               such constructs.
  2680.  
  2681.               Todo: discarding </p> is abviously a bug, it should be replaced by <br>.
  2682.             */
  2683.             InsertNodeAfterElement(pre, node);
  2684.             ReportError(doc, pre, node, MISSING_ENDTAG_BEFORE);
  2685.             ParseTag(doc, node, IgnoreWhitespace);
  2686.  
  2687.             newnode = InferredTag(doc, TidyTag_PRE);
  2688.             ReportError(doc, pre, newnode, INSERTING_TAG);
  2689.             pre = newnode;
  2690.             InsertNodeAfterElement(node, pre);
  2691.  
  2692.             continue;
  2693.         }
  2694.  
  2695.         if ( nodeIsP(node) )
  2696.         {
  2697.             if (node->type == StartTag)
  2698.             {
  2699.                 ReportError(doc, pre, node, USING_BR_INPLACE_OF);
  2700.  
  2701.                 /* trim white space before <p> in <pre>*/
  2702.                 TrimSpaces(doc, pre);
  2703.             
  2704.                 /* coerce both <p> and </p> to <br> */
  2705.                 CoerceNode(doc, node, TidyTag_BR, no, no);
  2706.                 FreeAttrs( doc, node ); /* discard align attribute etc. */
  2707.                 InsertNodeAtEnd( pre, node );
  2708.             }
  2709.             else
  2710.             {
  2711.                 ReportError(doc, pre, node, DISCARDING_UNEXPECTED);
  2712.                 FreeNode( doc, node);
  2713.             }
  2714.             continue;
  2715.         }
  2716.  
  2717.         if ( node->type == StartTag || node->type == StartEndTag )
  2718.         {
  2719.             /* trim white space before <br> */
  2720.             if ( nodeIsBR(node) )
  2721.                 TrimSpaces(doc, pre);
  2722.             
  2723.             InsertNodeAtEnd(pre, node);
  2724.             ParseTag(doc, node, Preformatted);
  2725.             continue;
  2726.         }
  2727.  
  2728.         /* discard unexpected tags */
  2729.         ReportError(doc, pre, node, DISCARDING_UNEXPECTED);
  2730.         FreeNode( doc, node);
  2731.     }
  2732.  
  2733.     ReportError(doc, pre, node, MISSING_ENDTAG_FOR);
  2734. }
  2735.  
  2736. void ParseOptGroup(TidyDocImpl* doc, Node *field, uint mode)
  2737. {
  2738. #pragma unused(mode)
  2739.  
  2740.     Lexer* lexer = doc->lexer;
  2741.     Node *node;
  2742.  
  2743.     lexer->insert = NULL;  /* defer implicit inline start tags */
  2744.  
  2745.     while ((node = GetToken(doc, IgnoreWhitespace)) != NULL)
  2746.     {
  2747.         if (node->tag == field->tag && node->type == EndTag)
  2748.         {
  2749.             FreeNode( doc, node);
  2750.             field->closed = yes;
  2751.             TrimSpaces(doc, field);
  2752.             return;
  2753.         }
  2754.  
  2755.         /* deal with comments etc. */
  2756.         if (InsertMisc(field, node))
  2757.             continue;
  2758.  
  2759.         if ( node->type == StartTag && 
  2760.              (nodeIsOPTION(node) || nodeIsOPTGROUP(node)) )
  2761.         {
  2762.             if ( nodeIsOPTGROUP(node) )
  2763.                 ReportError(doc, field, node, CANT_BE_NESTED);
  2764.  
  2765.             InsertNodeAtEnd(field, node);
  2766.             ParseTag(doc, node, MixedContent);
  2767.             continue;
  2768.         }
  2769.  
  2770.         /* discard unexpected tags */
  2771.         ReportError(doc, field, node, DISCARDING_UNEXPECTED );
  2772.         FreeNode( doc, node);
  2773.     }
  2774. }
  2775.  
  2776.  
  2777. void ParseSelect(TidyDocImpl* doc, Node *field, uint mode)
  2778. {
  2779. #pragma unused(mode)
  2780.  
  2781.     Lexer* lexer = doc->lexer;
  2782.     Node *node;
  2783.  
  2784.     lexer->insert = NULL;  /* defer implicit inline start tags */
  2785.  
  2786.     while ((node = GetToken(doc, IgnoreWhitespace)) != NULL)
  2787.     {
  2788.         if (node->tag == field->tag && node->type == EndTag)
  2789.         {
  2790.             FreeNode( doc, node);
  2791.             field->closed = yes;
  2792.             TrimSpaces(doc, field);
  2793.             return;
  2794.         }
  2795.  
  2796.         /* deal with comments etc. */
  2797.         if (InsertMisc(field, node))
  2798.             continue;
  2799.  
  2800.         if ( node->type == StartTag && 
  2801.              ( nodeIsOPTION(node)   ||
  2802.                nodeIsOPTGROUP(node) ||
  2803.                nodeIsSCRIPT(node)) 
  2804.            )
  2805.         {
  2806.             InsertNodeAtEnd(field, node);
  2807.             ParseTag(doc, node, IgnoreWhitespace);
  2808.             continue;
  2809.         }
  2810.  
  2811.         /* discard unexpected tags */
  2812.         ReportError(doc, field, node, DISCARDING_UNEXPECTED);
  2813.         FreeNode( doc, node);
  2814.     }
  2815.  
  2816.     ReportError(doc, field, node, MISSING_ENDTAG_FOR);
  2817. }
  2818.  
  2819. void ParseText(TidyDocImpl* doc, Node *field, uint mode)
  2820. {
  2821.     Lexer* lexer = doc->lexer;
  2822.     Node *node;
  2823.  
  2824.     lexer->insert = NULL;  /* defer implicit inline start tags */
  2825.  
  2826.     if ( nodeIsTEXTAREA(field) )
  2827.         mode = Preformatted;
  2828.     else
  2829.         mode = MixedContent;  /* kludge for font tags */
  2830.  
  2831.     while ((node = GetToken(doc, mode)) != NULL)
  2832.     {
  2833.         if (node->tag == field->tag && node->type == EndTag)
  2834.         {
  2835.             FreeNode( doc, node);
  2836.             field->closed = yes;
  2837.             TrimSpaces(doc, field);
  2838.             return;
  2839.         }
  2840.  
  2841.         /* deal with comments etc. */
  2842.         if (InsertMisc(field, node))
  2843.             continue;
  2844.  
  2845.         if (node->type == TextNode)
  2846.         {
  2847.             /* only called for 1st child */
  2848.             if (field->content == NULL && !(mode & Preformatted))
  2849.                 TrimSpaces(doc, field);
  2850.  
  2851.             if (node->start >= node->end)
  2852.             {
  2853.                 FreeNode( doc, node);
  2854.                 continue;
  2855.             }
  2856.  
  2857.             InsertNodeAtEnd(field, node);
  2858.             continue;
  2859.         }
  2860.  
  2861.         /* for textarea should all cases of < and & be escaped? */
  2862.  
  2863.         /* discard inline tags e.g. font */
  2864.         if (   node->tag 
  2865.             && node->tag->model & CM_INLINE
  2866.             && !(node->tag->model & CM_FIELD)) /* #487283 - fix by Lee Passey 25 Jan 02 */
  2867.         {
  2868.             ReportError(doc, field, node, DISCARDING_UNEXPECTED);
  2869.             FreeNode( doc, node);
  2870.             continue;
  2871.         }
  2872.  
  2873.         /* terminate element on other tags */
  2874.         if (!(field->tag->model & CM_OPT))
  2875.             ReportError(doc, field, node, MISSING_ENDTAG_BEFORE);
  2876.  
  2877.         UngetToken( doc );
  2878.         TrimSpaces(doc, field);
  2879.         return;
  2880.     }
  2881.  
  2882.     if (!(field->tag->model & CM_OPT))
  2883.         ReportError(doc, field, node, MISSING_ENDTAG_FOR);
  2884. }
  2885.  
  2886.  
  2887. void ParseTitle(TidyDocImpl* doc, Node *title, uint mode)
  2888. {
  2889. #pragma unused(mode)
  2890.  
  2891.     Node *node;
  2892.     while ((node = GetToken(doc, MixedContent)) != NULL)
  2893.     {
  2894.         if (node->tag == title->tag && node->type == StartTag)
  2895.         {
  2896.             ReportError(doc, title, node, COERCE_TO_ENDTAG);
  2897.             node->type = EndTag;
  2898.             UngetToken( doc );
  2899.             continue;
  2900.         }
  2901.         else if (node->tag == title->tag && node->type == EndTag)
  2902.         {
  2903.             FreeNode( doc, node);
  2904.             title->closed = yes;
  2905.             TrimSpaces(doc, title);
  2906.             return;
  2907.         }
  2908.  
  2909.         if (node->type == TextNode)
  2910.         {
  2911.             /* only called for 1st child */
  2912.             if (title->content == NULL)
  2913.                 TrimInitialSpace(doc, title, node);
  2914.  
  2915.             if (node->start >= node->end)
  2916.             {
  2917.                 FreeNode( doc, node);
  2918.                 continue;
  2919.             }
  2920.  
  2921.             InsertNodeAtEnd(title, node);
  2922.             continue;
  2923.         }
  2924.  
  2925.         /* deal with comments etc. */
  2926.         if (InsertMisc(title, node))
  2927.             continue;
  2928.  
  2929.         /* discard unknown tags */
  2930.         if (node->tag == NULL)
  2931.         {
  2932.             ReportError(doc, title, node, DISCARDING_UNEXPECTED);
  2933.             FreeNode( doc, node);
  2934.             continue;
  2935.         }
  2936.  
  2937.         /* pushback unexpected tokens */
  2938.         ReportError(doc, title, node, MISSING_ENDTAG_BEFORE);
  2939.         UngetToken( doc );
  2940.         TrimSpaces(doc, title);
  2941.         return;
  2942.     }
  2943.  
  2944.     ReportError(doc, title, node, MISSING_ENDTAG_FOR);
  2945. }
  2946.  
  2947. /*
  2948.   This isn't quite right for CDATA content as it recognises
  2949.   tags within the content and parses them accordingly.
  2950.   This will unfortunately screw up scripts which include
  2951.   < + letter,  < + !, < + ?  or  < + / + letter
  2952. */
  2953.  
  2954. void ParseScript(TidyDocImpl* doc, Node *script, uint mode)
  2955. {
  2956. #pragma unused(mode)
  2957.  
  2958.     Node *node;
  2959.     
  2960.     doc->lexer->parent = script;
  2961.     node = GetToken(doc, CdataContent);
  2962.     doc->lexer->parent = NULL;
  2963.  
  2964.     if (node)
  2965.     {
  2966.         InsertNodeAtEnd(script, node);
  2967.     }
  2968.     else
  2969.     {
  2970.         /* handle e.g. a document like "<script>" */
  2971.         ReportError(doc, script, NULL, MISSING_ENDTAG_FOR);
  2972.         return;
  2973.     }
  2974.  
  2975.     node = GetToken(doc, IgnoreWhitespace);
  2976.  
  2977.     if (!(node && node->type == EndTag && node->tag->id == script->tag->id))
  2978.     {
  2979.         ReportError(doc, script, node, MISSING_ENDTAG_FOR);
  2980.  
  2981.         if (node)
  2982.             UngetToken(doc);
  2983.     }
  2984.     else
  2985.     {
  2986.         FreeNode(doc, node);
  2987.     }
  2988. }
  2989.  
  2990. Bool IsJavaScript(Node *node)
  2991. {
  2992.     Bool result = no;
  2993.     AttVal *attr;
  2994.  
  2995.     if (node->attributes == NULL)
  2996.         return yes;
  2997.  
  2998.     for (attr = node->attributes; attr; attr = attr->next)
  2999.     {
  3000.         if ( (attrIsLANGUAGE(attr) || attrIsTYPE(attr))
  3001.              && AttrContains(attr, "javascript") )
  3002.         {
  3003.             result = yes;
  3004.             break;
  3005.         }
  3006.     }
  3007.  
  3008.     return result;
  3009. }
  3010.  
  3011. void ParseHead(TidyDocImpl* doc, Node *head, uint mode)
  3012. {
  3013. #pragma unused(mode)
  3014.  
  3015.     Lexer* lexer = doc->lexer;
  3016.     Node *node;
  3017.     int HasTitle = 0;
  3018.     int HasBase = 0;
  3019.  
  3020.     while ((node = GetToken(doc, IgnoreWhitespace)) != NULL)
  3021.     {
  3022.         if (node->tag == head->tag && node->type == EndTag)
  3023.         {
  3024.             FreeNode( doc, node);
  3025.             head->closed = yes;
  3026.             break;
  3027.         }
  3028.  
  3029.         /* find and discard multiple <head> elements */
  3030.         /* find and discard <html> in <head> elements */
  3031.         if ((node->tag == head->tag || nodeIsHTML(node)) && node->type == StartTag)
  3032.         {
  3033.             ReportError(doc, head, node, DISCARDING_UNEXPECTED);
  3034.             FreeNode(doc, node);
  3035.             continue;
  3036.         }
  3037.  
  3038.         if (node->type == TextNode)
  3039.         {
  3040.             ReportError(doc, head, node, TAG_NOT_ALLOWED_IN);
  3041.             UngetToken( doc );
  3042.             break;
  3043.         }
  3044.  
  3045.         if (node->type == ProcInsTag && node->element &&
  3046.             tmbstrcmp(node->element, "xml-stylesheet") == 0)
  3047.         {
  3048.             ReportError(doc, head, node, TAG_NOT_ALLOWED_IN);
  3049.             InsertNodeBeforeElement(FindHTML(doc), node);
  3050.             continue;
  3051.         }
  3052.  
  3053.         /* deal with comments etc. */
  3054.         if (InsertMisc(head, node))
  3055.             continue;
  3056.  
  3057.         if (node->type == DocTypeTag)
  3058.         {
  3059.             InsertDocType(doc, head, node);
  3060.             continue;
  3061.         }
  3062.  
  3063.         /* discard unknown tags */
  3064.         if (node->tag == NULL)
  3065.         {
  3066.             ReportError(doc, head, node, DISCARDING_UNEXPECTED);
  3067.             FreeNode( doc, node);
  3068.             continue;
  3069.         }
  3070.         
  3071.         /*
  3072.          if it doesn't belong in the head then
  3073.          treat as implicit head of head and deal
  3074.          with as part of the body
  3075.         */
  3076.         if (!(node->tag->model & CM_HEAD))
  3077.         {
  3078.             /* #545067 Implicit closing of head broken - warn only for XHTML input */
  3079.             if ( lexer->isvoyager )
  3080.                 ReportError(doc, head, node, TAG_NOT_ALLOWED_IN );
  3081.             UngetToken( doc );
  3082.             break;
  3083.         }
  3084.  
  3085.         if (node->type == StartTag || node->type == StartEndTag)
  3086.         {
  3087.             if ( nodeIsTITLE(node) )
  3088.             {
  3089.                 ++HasTitle;
  3090.  
  3091.                 if (HasTitle > 1)
  3092.                     if (head)
  3093.                         ReportError(doc, head, node, TOO_MANY_ELEMENTS_IN);
  3094.                     else
  3095.                         ReportError(doc, head, node, TOO_MANY_ELEMENTS);
  3096.             }
  3097.             else if ( nodeIsBASE(node) )
  3098.             {
  3099.                 ++HasBase;
  3100.  
  3101.                 if (HasBase > 1)
  3102.                     if (head)
  3103.                         ReportError(doc, head, node, TOO_MANY_ELEMENTS_IN);
  3104.                     else
  3105.                         ReportError(doc, head, node, TOO_MANY_ELEMENTS);
  3106.             }
  3107.             else if ( nodeIsNOSCRIPT(node) )
  3108.             {
  3109.                 ReportError(doc, head, node, TAG_NOT_ALLOWED_IN);
  3110.             }
  3111.  
  3112. #ifdef AUTO_INPUT_ENCODING
  3113.             else if (nodeIsMETA(node))
  3114.             {
  3115.                 AttVal * httpEquiv = AttrGetById(node, TidyAttr_HTTP_EQUIV);
  3116.                 AttVal * content = AttrGetById(node, TidyAttr_CONTENT);
  3117.                 if (httpEquiv && AttrValueIs(httpEquiv, "Content-Type") && AttrHasValue(content))
  3118.                 {
  3119.                     tmbstr val, charset;
  3120.                     uint end = 0;
  3121.                     val = charset = tmbstrdup(content->value);
  3122.                     val = tmbstrtolower(val);
  3123.                     val = strstr(content->value, "charset");
  3124.                     
  3125.                     if (val)
  3126.                         val += 7;
  3127.  
  3128.                     while(val && *val && (IsWhite((tchar)*val) ||
  3129.                           *val == '=' || *val == '"' || *val == '\''))
  3130.                         ++val;
  3131.  
  3132.                     while(val && val[end] && !(IsWhite((tchar)val[end]) ||
  3133.                           val[end] == '"' || val[end] == '\'' || val[end] == ';'))
  3134.                         ++end;
  3135.  
  3136.                     if (val && end)
  3137.                     {
  3138.                         tmbstr encoding = tmbstrndup(val, end);
  3139.                         uint id = GetEncodingIdFromName(encoding);
  3140.  
  3141.                         /* todo: detect mismatch with BOM/XMLDecl/declared */
  3142.                         /* todo: error for unsupported encodings */
  3143.                         /* todo: try to re-init transcoder */
  3144.                         /* todo: change input/output encoding settings */
  3145.                         /* todo: store id in StreamIn */
  3146.  
  3147.                         MemFree(encoding);
  3148.                     }
  3149.  
  3150.                     MemFree(charset);
  3151.                 }
  3152.             }
  3153. #endif /* AUTO_INPUT_ENCODING */
  3154.  
  3155.             InsertNodeAtEnd(head, node);
  3156.             ParseTag(doc, node, IgnoreWhitespace);
  3157.             continue;
  3158.         }
  3159.  
  3160.         /* discard unexpected text nodes and end tags */
  3161.         ReportError(doc, head, node, DISCARDING_UNEXPECTED);
  3162.         FreeNode( doc, node);
  3163.     }
  3164. }
  3165.  
  3166. void ParseBody(TidyDocImpl* doc, Node *body, uint mode)
  3167. {
  3168.     Lexer* lexer = doc->lexer;
  3169.     Node *node;
  3170.     Bool checkstack, iswhitenode;
  3171.  
  3172.     mode = IgnoreWhitespace;
  3173.     checkstack = yes;
  3174.  
  3175.     BumpObject( doc, body->parent );
  3176.  
  3177.     while ((node = GetToken(doc, mode)) != NULL)
  3178.     {
  3179.         /* find and discard multiple <body> elements */
  3180.         if (node->tag == body->tag && node->type == StartTag)
  3181.         {
  3182.             ReportError(doc, body, node, DISCARDING_UNEXPECTED);
  3183.             FreeNode(doc, node);
  3184.             continue;
  3185.         }
  3186.  
  3187.         /* #538536 Extra endtags not detected */
  3188.         if ( nodeIsHTML(node) )
  3189.         {
  3190.             if (node->type == StartTag || node->type == StartEndTag || lexer->seenEndHtml) 
  3191.                 ReportError(doc, body, node, DISCARDING_UNEXPECTED);
  3192.             else
  3193.                 lexer->seenEndHtml = 1;
  3194.  
  3195.             FreeNode( doc, node);
  3196.             continue;
  3197.         }
  3198.  
  3199.         if ( lexer->seenEndBody && 
  3200.              ( node->type == StartTag ||
  3201.                node->type == EndTag   ||
  3202.                node->type == StartEndTag ) )
  3203.         {
  3204.             ReportError(doc, body, node, CONTENT_AFTER_BODY );
  3205.         }
  3206.  
  3207.         if ( node->tag == body->tag && node->type == EndTag )
  3208.         {
  3209.             body->closed = yes;
  3210.             TrimSpaces(doc, body);
  3211.             FreeNode( doc, node);
  3212.             lexer->seenEndBody = 1;
  3213.             mode = IgnoreWhitespace;
  3214.  
  3215.             if ( nodeIsNOFRAMES(body->parent) )
  3216.                 break;
  3217.  
  3218.             continue;
  3219.         }
  3220.  
  3221.         if ( nodeIsNOFRAMES(node) )
  3222.         {
  3223.             if (node->type == StartTag)
  3224.             {
  3225.                 InsertNodeAtEnd(body, node);
  3226.                 ParseBlock(doc, node, mode);
  3227.                 continue;
  3228.             }
  3229.  
  3230.             if (node->type == EndTag && nodeIsNOFRAMES(body->parent) )
  3231.             {
  3232.                 TrimSpaces(doc, body);
  3233.                 UngetToken( doc );
  3234.                 break;
  3235.             }
  3236.         }
  3237.  
  3238.         if ( (nodeIsFRAME(node) || nodeIsFRAMESET(node))
  3239.              && nodeIsNOFRAMES(body->parent) )
  3240.         {
  3241.             TrimSpaces(doc, body);
  3242.             UngetToken( doc );
  3243.             break;
  3244.         }
  3245.         
  3246.         iswhitenode = no;
  3247.  
  3248.         if ( node->type == TextNode &&
  3249.              node->end <= node->start + 1 &&
  3250.              lexer->lexbuf[node->start] == ' ' )
  3251.             iswhitenode = yes;
  3252.  
  3253.         /* deal with comments etc. */
  3254.         if (InsertMisc(body, node))
  3255.             continue;
  3256.  
  3257.         /* #538536 Extra endtags not detected */
  3258. #if 0
  3259.         if ( lexer->seenEndBody == 1 && !iswhitenode )
  3260.         {
  3261.             ++lexer->seenEndBody;
  3262.             ReportError(doc, body, node, CONTENT_AFTER_BODY);
  3263.         }
  3264. #endif
  3265.  
  3266.         /* mixed content model permits text */
  3267.         if (node->type == TextNode)
  3268.         {
  3269.             if (iswhitenode && mode == IgnoreWhitespace)
  3270.             {
  3271.                 FreeNode( doc, node);
  3272.                 continue;
  3273.             }
  3274.  
  3275.             /* HTML 2 and HTML4 strict don't allow text here */
  3276.             ConstrainVersion(doc, ~(VERS_HTML40_STRICT | VERS_HTML20));
  3277.  
  3278.             if (checkstack)
  3279.             {
  3280.                 checkstack = no;
  3281.  
  3282.                 if ( InlineDup(doc, node) > 0 )
  3283.                     continue;
  3284.             }
  3285.  
  3286.             InsertNodeAtEnd(body, node);
  3287.             mode = MixedContent;
  3288.             continue;
  3289.         }
  3290.  
  3291.         if (node->type == DocTypeTag)
  3292.         {
  3293.             InsertDocType(doc, body, node);
  3294.             continue;
  3295.         }
  3296.         /* discard unknown  and PARAM tags */
  3297.         if ( node->tag == NULL || nodeIsPARAM(node) )
  3298.         {
  3299.             ReportError(doc, body, node, DISCARDING_UNEXPECTED);
  3300.             FreeNode( doc, node);
  3301.             continue;
  3302.         }
  3303.  
  3304.         /*
  3305.           Netscape allows LI and DD directly in BODY
  3306.           We infer UL or DL respectively and use this
  3307.           Bool to exclude block-level elements so as
  3308.           to match Netscape's observed behaviour.
  3309.         */
  3310.         lexer->excludeBlocks = no;
  3311.         
  3312.         if ( nodeIsINPUT(node) ||
  3313.              (!nodeHasCM(node, CM_BLOCK) && !nodeHasCM(node, CM_INLINE))
  3314.            )
  3315.         {
  3316.             /* avoid this error message being issued twice */
  3317.             if (!(node->tag->model & CM_HEAD))
  3318.                 ReportError(doc, body, node, TAG_NOT_ALLOWED_IN);
  3319.  
  3320.             if (node->tag->model & CM_HTML)
  3321.             {
  3322.                 /* copy body attributes if current body was inferred */
  3323.                 if ( nodeIsBODY(node) && body->implicit 
  3324.                      && body->attributes == NULL )
  3325.                 {
  3326.                     body->attributes = node->attributes;
  3327.                     node->attributes = NULL;
  3328.                 }
  3329.  
  3330.                 FreeNode( doc, node);
  3331.                 continue;
  3332.             }
  3333.  
  3334.             if (node->tag->model & CM_HEAD)
  3335.             {
  3336.                 MoveToHead(doc, body, node);
  3337.                 continue;
  3338.             }
  3339.  
  3340.             if (node->tag->model & CM_LIST)
  3341.             {
  3342.                 UngetToken( doc );
  3343.                 node = InferredTag(doc, TidyTag_UL);
  3344.                 AddClass( doc, node, "noindent" );
  3345.                 lexer->excludeBlocks = yes;
  3346.             }
  3347.             else if (node->tag->model & CM_DEFLIST)
  3348.             {
  3349.                 UngetToken( doc );
  3350.                 node = InferredTag(doc, TidyTag_DL);
  3351.                 lexer->excludeBlocks = yes;
  3352.             }
  3353.             else if (node->tag->model & (CM_TABLE | CM_ROWGRP | CM_ROW))
  3354.             {
  3355.                 UngetToken( doc );
  3356.                 node = InferredTag(doc, TidyTag_TABLE);
  3357.                 lexer->excludeBlocks = yes;
  3358.             }
  3359.             else if ( nodeIsINPUT(node) )
  3360.             {
  3361.                 UngetToken( doc );
  3362.                 node = InferredTag(doc, TidyTag_FORM);
  3363.                 lexer->excludeBlocks = yes;
  3364.             }
  3365.             else
  3366.             {
  3367.                 if ( !nodeHasCM(node, CM_ROW | CM_FIELD) )
  3368.                 {
  3369.                     UngetToken( doc );
  3370.                     return;
  3371.                 }
  3372.  
  3373.                 /* ignore </td> </th> <option> etc. */
  3374.                 FreeNode( doc, node );
  3375.                 continue;
  3376.             }
  3377.         }
  3378.  
  3379.         if (node->type == EndTag)
  3380.         {
  3381.             if ( nodeIsBR(node) )
  3382.                 node->type = StartTag;
  3383.             else if ( nodeIsP(node) )
  3384.             {
  3385.                 CoerceNode(doc, node, TidyTag_BR, no, no);
  3386.                 FreeAttrs( doc, node ); /* discard align attribute etc. */
  3387.                 InsertNodeAtEnd(body, node);
  3388.                 node = InferredTag(doc, TidyTag_BR);
  3389.             }
  3390.             else if ( nodeHasCM(node, CM_INLINE) )
  3391.                 PopInline( doc, node );
  3392.         }
  3393.  
  3394.         if (node->type == StartTag || node->type == StartEndTag)
  3395.         {
  3396.             if ( nodeHasCM(node, CM_INLINE) && !nodeHasCM(node, CM_MIXED) )
  3397.             {
  3398.                 /* HTML4 strict doesn't allow inline content here */
  3399.                 /* but HTML2 does allow img elements as children of body */
  3400.                 if ( nodeIsIMG(node) )
  3401.                     ConstrainVersion(doc, ~VERS_HTML40_STRICT);
  3402.                 else
  3403.                     ConstrainVersion(doc, ~(VERS_HTML40_STRICT|VERS_HTML20));
  3404.  
  3405.                 if (checkstack && !node->implicit)
  3406.                 {
  3407.                     checkstack = no;
  3408.  
  3409.                     if ( InlineDup(doc, node) > 0 )
  3410.                         continue;
  3411.                 }
  3412.  
  3413.                 mode = MixedContent;
  3414.             }
  3415.             else
  3416.             {
  3417.                 checkstack = yes;
  3418.                 mode = IgnoreWhitespace;
  3419.             }
  3420.  
  3421.             if (node->implicit)
  3422.                 ReportError(doc, body, node, INSERTING_TAG);
  3423.  
  3424.             InsertNodeAtEnd(body, node);
  3425.             ParseTag(doc, node, mode);
  3426.             continue;
  3427.         }
  3428.  
  3429.         /* discard unexpected tags */
  3430.         ReportError(doc, body, node, DISCARDING_UNEXPECTED);
  3431.         FreeNode( doc, node);
  3432.     }
  3433. }
  3434.  
  3435. void ParseNoFrames(TidyDocImpl* doc, Node *noframes, uint mode)
  3436. {
  3437.     Lexer* lexer = doc->lexer;
  3438.     Node *node;
  3439.     Bool checkstack;
  3440.  
  3441.     if ( cfg(doc, TidyAccessibilityCheckLevel) == 0 )
  3442.     {
  3443.         doc->badAccess |=  USING_NOFRAMES;
  3444.     }
  3445.     mode = IgnoreWhitespace;
  3446.     checkstack = yes;
  3447.  
  3448.     while ( (node = GetToken(doc, mode)) != NULL )
  3449.     {
  3450.         if ( node->tag == noframes->tag && node->type == EndTag )
  3451.         {
  3452.             FreeNode( doc, node);
  3453.             noframes->closed = yes;
  3454.             TrimSpaces(doc, noframes);
  3455.             return;
  3456.         }
  3457.  
  3458.         if ( nodeIsFRAME(node) || nodeIsFRAMESET(node) )
  3459.         {
  3460.             TrimSpaces(doc, noframes);
  3461.             if (node->type == EndTag)
  3462.             {
  3463.                 ReportError(doc, noframes, node, DISCARDING_UNEXPECTED);
  3464.                 FreeNode( doc, node);       /* Throw it away */
  3465.             }
  3466.             else
  3467.             {
  3468.                 ReportError(doc, noframes, node, MISSING_ENDTAG_BEFORE);
  3469.                 UngetToken( doc );
  3470.             }
  3471.             return;
  3472.         }
  3473.  
  3474.         if ( nodeIsHTML(node) )
  3475.         {
  3476.             if (node->type == StartTag || node->type == StartEndTag)
  3477.                 ReportError(doc, noframes, node, DISCARDING_UNEXPECTED);
  3478.  
  3479.             FreeNode( doc, node);
  3480.             continue;
  3481.         }
  3482.  
  3483.         /* deal with comments etc. */
  3484.         if (InsertMisc(noframes, node))
  3485.             continue;
  3486.  
  3487.         if ( nodeIsBODY(node) && node->type == StartTag )
  3488.         {
  3489.             Bool seen_body = lexer->seenEndBody;
  3490.             InsertNodeAtEnd(noframes, node);
  3491.             ParseTag(doc, node, IgnoreWhitespace /*MixedContent*/);
  3492.  
  3493.             /* fix for bug http://tidy.sf.net/bug/887259 */
  3494.             if (seen_body && FindBody(doc) != node)
  3495.             {
  3496.                 CoerceNode(doc, node, TidyTag_DIV, no, no);
  3497.                 MoveNodeToBody(doc, node);
  3498.             }
  3499.             continue;
  3500.         }
  3501.  
  3502.         /* implicit body element inferred */
  3503.         if (node->type == TextNode || (node->tag && node->type != EndTag))
  3504.         {
  3505.             if ( lexer->seenEndBody )
  3506.             {
  3507.                 Node *body = FindBody( doc );
  3508.                 if ( node->type == TextNode )
  3509.                 {
  3510.                     UngetToken( doc );
  3511.                     node = InferredTag(doc, TidyTag_P);
  3512.                     ReportError(doc, noframes, node, CONTENT_AFTER_BODY );
  3513.                 }
  3514.                 InsertNodeAtEnd( body, node );
  3515.             }
  3516.             else
  3517.             {
  3518.                 UngetToken( doc );
  3519.                 node = InferredTag(doc, TidyTag_BODY);
  3520.                 if ( cfgBool(doc, TidyXmlOut) )
  3521.                     ReportError(doc, noframes, node, INSERTING_TAG);
  3522.                 InsertNodeAtEnd( noframes, node );
  3523.             }
  3524.  
  3525.             ParseTag( doc, node, IgnoreWhitespace /*MixedContent*/ );
  3526.             continue;
  3527.         }
  3528.  
  3529.         /* discard unexpected end tags */
  3530.         ReportError(doc, noframes, node, DISCARDING_UNEXPECTED);
  3531.         FreeNode( doc, node);
  3532.     }
  3533.  
  3534.     ReportError(doc, noframes, node, MISSING_ENDTAG_FOR);
  3535. }
  3536.  
  3537. void ParseFrameSet(TidyDocImpl* doc, Node *frameset, uint mode)
  3538. {
  3539. #pragma unused(mode)
  3540.  
  3541.     Lexer* lexer = doc->lexer;
  3542.     Node *node;
  3543.  
  3544.     if ( cfg(doc, TidyAccessibilityCheckLevel) == 0 )
  3545.     {
  3546.         doc->badAccess |=  USING_FRAMES;
  3547.     }
  3548.     
  3549.     while ((node = GetToken(doc, IgnoreWhitespace)) != NULL)
  3550.     {
  3551.         if (node->tag == frameset->tag && node->type == EndTag)
  3552.         {
  3553.             FreeNode( doc, node);
  3554.             frameset->closed = yes;
  3555.             TrimSpaces(doc, frameset);
  3556.             return;
  3557.         }
  3558.  
  3559.         /* deal with comments etc. */
  3560.         if (InsertMisc(frameset, node))
  3561.             continue;
  3562.  
  3563.         if (node->tag == NULL)
  3564.         {
  3565.             ReportError(doc, frameset, node, DISCARDING_UNEXPECTED);
  3566.             FreeNode( doc, node);
  3567.             continue; 
  3568.         }
  3569.  
  3570.         if (node->type == StartTag || node->type == StartEndTag)
  3571.         {
  3572.             if (node->tag && node->tag->model & CM_HEAD)
  3573.             {
  3574.                 MoveToHead(doc, frameset, node);
  3575.                 continue;
  3576.             }
  3577.         }
  3578.  
  3579.         if ( nodeIsBODY(node) )
  3580.         {
  3581.             UngetToken( doc );
  3582.             node = InferredTag(doc, TidyTag_NOFRAMES);
  3583.             ReportError(doc, frameset, node, INSERTING_TAG);
  3584.         }
  3585.  
  3586.         if (node->type == StartTag && (node->tag->model & CM_FRAMES))
  3587.         {
  3588.             InsertNodeAtEnd(frameset, node);
  3589.             lexer->excludeBlocks = no;
  3590.             ParseTag(doc, node, MixedContent);
  3591.             continue;
  3592.         }
  3593.         else if (node->type == StartEndTag && (node->tag->model & CM_FRAMES))
  3594.         {
  3595.             InsertNodeAtEnd(frameset, node);
  3596.             continue;
  3597.         }
  3598.  
  3599.         /* discard unexpected tags */
  3600.         ReportError(doc, frameset, node, DISCARDING_UNEXPECTED);
  3601.         FreeNode( doc, node);
  3602.     }
  3603.  
  3604.     ReportError(doc, frameset, node, MISSING_ENDTAG_FOR);
  3605. }
  3606.  
  3607. void ParseHTML(TidyDocImpl* doc, Node *html, uint mode)
  3608. {
  3609.     Node *node, *head;
  3610.     Node *frameset = NULL;
  3611.     Node *noframes = NULL;
  3612.  
  3613.     SetOptionBool( doc, TidyXmlTags, no );
  3614.  
  3615.     for (;;)
  3616.     {
  3617.         node = GetToken(doc, IgnoreWhitespace);
  3618.  
  3619.         if (node == NULL)
  3620.         {
  3621.             node = InferredTag(doc, TidyTag_HEAD);
  3622.             break;
  3623.         }
  3624.  
  3625.         if ( nodeIsHEAD(node) )
  3626.             break;
  3627.  
  3628.         if (node->tag == html->tag && node->type == EndTag)
  3629.         {
  3630.             ReportError(doc, html, node, DISCARDING_UNEXPECTED);
  3631.             FreeNode( doc, node);
  3632.             continue;
  3633.         }
  3634.  
  3635.         /* find and discard multiple <html> elements */
  3636.         if (node->tag == html->tag && node->type == StartTag)
  3637.         {
  3638.             ReportError(doc, html, node, DISCARDING_UNEXPECTED);
  3639.             FreeNode(doc, node);
  3640.             continue;
  3641.         }
  3642.  
  3643.         /* deal with comments etc. */
  3644.         if (InsertMisc(html, node))
  3645.             continue;
  3646.  
  3647.         UngetToken( doc );
  3648.         node = InferredTag(doc, TidyTag_HEAD);
  3649.         break;
  3650.     }
  3651.  
  3652.     head = node;
  3653.     InsertNodeAtEnd(html, head);
  3654.     ParseHead(doc, head, mode);
  3655.  
  3656.     for (;;)
  3657.     {
  3658.         node = GetToken(doc, IgnoreWhitespace);
  3659.  
  3660.         if (node == NULL)
  3661.         {
  3662.             if (frameset == NULL) /* implied body */
  3663.             {
  3664.                 node = InferredTag(doc, TidyTag_BODY);
  3665.                 InsertNodeAtEnd(html, node);
  3666.                 ParseBody(doc, node, mode);
  3667.             }
  3668.  
  3669.             return;
  3670.         }
  3671.  
  3672.         /* robustly handle html tags */
  3673.         if (node->tag == html->tag)
  3674.         {
  3675.             if (node->type != StartTag && frameset == NULL)
  3676.                 ReportError(doc, html, node, DISCARDING_UNEXPECTED);
  3677.  
  3678.             FreeNode( doc, node);
  3679.             continue;
  3680.         }
  3681.  
  3682.         /* deal with comments etc. */
  3683.         if (InsertMisc(html, node))
  3684.             continue;
  3685.  
  3686.         /* if frameset document coerce <body> to <noframes> */
  3687.         if ( nodeIsBODY(node) )
  3688.         {
  3689.             if (node->type != StartTag)
  3690.             {
  3691.                 ReportError(doc, html, node, DISCARDING_UNEXPECTED);
  3692.                 FreeNode( doc, node);
  3693.                 continue;
  3694.             }
  3695.  
  3696.             if ( cfg(doc, TidyAccessibilityCheckLevel) == 0 )
  3697.             {
  3698.                 if (frameset != NULL)
  3699.                 {
  3700.                     UngetToken( doc );
  3701.  
  3702.                     if (noframes == NULL)
  3703.                     {
  3704.                         noframes = InferredTag(doc, TidyTag_NOFRAMES);
  3705.                         InsertNodeAtEnd(frameset, noframes);
  3706.                         ReportError(doc, html, noframes, INSERTING_TAG);
  3707.                     }
  3708.  
  3709.                     ParseTag(doc, noframes, mode);
  3710.                     continue;
  3711.                 }
  3712.             }
  3713.  
  3714.             ConstrainVersion(doc, ~VERS_FRAMESET);
  3715.             break;  /* to parse body */
  3716.         }
  3717.  
  3718.         /* flag an error if we see more than one frameset */
  3719.         if ( nodeIsFRAMESET(node) )
  3720.         {
  3721.             if (node->type != StartTag)
  3722.             {
  3723.                 ReportError(doc, html, node, DISCARDING_UNEXPECTED);
  3724.                 FreeNode( doc, node);
  3725.                 continue;
  3726.             }
  3727.  
  3728.             if (frameset != NULL)
  3729.                 ReportFatal(doc, html, node, DUPLICATE_FRAMESET);
  3730.             else
  3731.                 frameset = node;
  3732.  
  3733.             InsertNodeAtEnd(html, node);
  3734.             ParseTag(doc, node, mode);
  3735.  
  3736.             /*
  3737.               see if it includes a noframes element so
  3738.               that we can merge subsequent noframes elements
  3739.             */
  3740.  
  3741.             for (node = frameset->content; node; node = node->next)
  3742.             {
  3743.                 if ( nodeIsNOFRAMES(node) )
  3744.                     noframes = node;
  3745.             }
  3746.             continue;
  3747.         }
  3748.  
  3749.         /* if not a frameset document coerce <noframes> to <body> */
  3750.         if ( nodeIsNOFRAMES(node) )
  3751.         {
  3752.             if (node->type != StartTag)
  3753.             {
  3754.                 ReportError(doc, html, node, DISCARDING_UNEXPECTED);
  3755.                 FreeNode( doc, node);
  3756.                 continue;
  3757.             }
  3758.  
  3759.             if (frameset == NULL)
  3760.             {
  3761.                 ReportError(doc, html, node, DISCARDING_UNEXPECTED);
  3762.                 FreeNode( doc, node);
  3763.                 node = InferredTag(doc, TidyTag_BODY);
  3764.                 break;
  3765.             }
  3766.  
  3767.             if (noframes == NULL)
  3768.             {
  3769.                 noframes = node;
  3770.                 InsertNodeAtEnd(frameset, noframes);
  3771.             }
  3772.             else
  3773.                 FreeNode( doc, node);
  3774.  
  3775.             ParseTag(doc, noframes, mode);
  3776.             continue;
  3777.         }
  3778.  
  3779.         if (node->type == StartTag || node->type == StartEndTag)
  3780.         {
  3781.             if (node->tag && node->tag->model & CM_HEAD)
  3782.             {
  3783.                 MoveToHead(doc, html, node);
  3784.                 continue;
  3785.             }
  3786.  
  3787.             /* discard illegal frame element following a frameset */
  3788.             if ( frameset != NULL && nodeIsFRAME(node) )
  3789.             {
  3790.                 ReportError(doc, html, node, DISCARDING_UNEXPECTED);
  3791.                 continue;
  3792.             }
  3793.         }
  3794.  
  3795.         UngetToken( doc );
  3796.  
  3797.         /* insert other content into noframes element */
  3798.  
  3799.         if (frameset)
  3800.         {
  3801.             if (noframes == NULL)
  3802.             {
  3803.                 noframes = InferredTag(doc, TidyTag_NOFRAMES);
  3804.                 InsertNodeAtEnd(frameset, noframes);
  3805.             }
  3806.             else
  3807.                 ReportError(doc, html, node, NOFRAMES_CONTENT);
  3808.  
  3809.             ConstrainVersion(doc, VERS_FRAMESET);
  3810.             ParseTag(doc, noframes, mode);
  3811.             continue;
  3812.         }
  3813.  
  3814.         node = InferredTag(doc, TidyTag_BODY);
  3815.         ConstrainVersion(doc, ~VERS_FRAMESET);
  3816.         break;
  3817.     }
  3818.  
  3819.     /* node must be body */
  3820.  
  3821.     InsertNodeAtEnd(html, node);
  3822.     ParseTag(doc, node, mode);
  3823. }
  3824.  
  3825. static void EncloseBodyText(TidyDocImpl* doc)
  3826. {
  3827.     Node* node;
  3828.     Node* body = FindBody(doc);
  3829.  
  3830.     if (!body)
  3831.         return;
  3832.  
  3833.     node = body->content;
  3834.  
  3835.     while (node)
  3836.     {
  3837.         if ((node->type == TextNode && !IsBlank(doc->lexer, node)) ||
  3838.             (nodeIsElement(node) && nodeHasCM(node, CM_INLINE)))
  3839.         {
  3840.             Node* p = InferredTag(doc, TidyTag_P);
  3841.             InsertNodeBeforeElement(node, p);
  3842.             while (node && (!nodeIsElement(node) || nodeHasCM(node, CM_INLINE)))
  3843.             {
  3844.                 Node* next = node->next;
  3845.                 RemoveNode(node);
  3846.                 InsertNodeAtEnd(p, node);
  3847.                 node = next;
  3848.             }
  3849.             TrimSpaces(doc, p);
  3850.             continue;
  3851.         }
  3852.         node = node->next;
  3853.     }
  3854. }
  3855.  
  3856. static void EncloseBlockText(TidyDocImpl* doc, Node* node)
  3857. {
  3858.     Node *next;
  3859.     Node *block;
  3860.  
  3861.     while (node)
  3862.     {
  3863.         next = node->next;
  3864.  
  3865.         if (node->content)
  3866.             EncloseBlockText(doc, node->content);
  3867.  
  3868.         if (!(nodeIsFORM(node) || nodeIsNOSCRIPT(node) || nodeIsBLOCKQUOTE(node)) || !node->content)
  3869.         {
  3870.             node = next;
  3871.             continue;
  3872.         }
  3873.  
  3874.         block = node->content;
  3875.  
  3876.         if ((block->type == TextNode && !IsBlank(doc->lexer, block)) ||
  3877.             (nodeIsElement(block) && nodeHasCM(block, CM_INLINE)))
  3878.         {
  3879.             Node* p = InferredTag(doc, TidyTag_P);
  3880.             InsertNodeBeforeElement(block, p);
  3881.             while (block && (!nodeIsElement(block) || nodeHasCM(block, CM_INLINE)))
  3882.             {
  3883.                 Node* tempNext = block->next;
  3884.                 RemoveNode(block);
  3885.                 InsertNodeAtEnd(p, block);
  3886.                 block = tempNext;
  3887.             }
  3888.             TrimSpaces(doc, p);
  3889.             continue;
  3890.         }
  3891.  
  3892.         node = next;
  3893.     }
  3894. }
  3895.  
  3896. static void ReplaceObsoleteElements(TidyDocImpl* doc, Node* node)
  3897. {
  3898.     Node *next;
  3899.  
  3900.     while (node)
  3901.     {
  3902.         next = node->next;
  3903.  
  3904.         if (nodeIsDIR(node) || nodeIsMENU(node))
  3905.             CoerceNode(doc, node, TidyTag_UL, yes, yes);
  3906.  
  3907.         if (nodeIsXMP(node) || nodeIsLISTING(node) ||
  3908.             (node->tag && node->tag->id == TidyTag_PLAINTEXT))
  3909.             CoerceNode(doc, node, TidyTag_PRE, yes, yes);
  3910.  
  3911.         if (node->content)
  3912.             ReplaceObsoleteElements(doc, node->content);
  3913.  
  3914.         node = next;
  3915.     }
  3916. }
  3917.  
  3918. static void AttributeChecks(TidyDocImpl* doc, Node* node)
  3919. {
  3920.     Node *next;
  3921.  
  3922.     while (node)
  3923.     {
  3924.         next = node->next;
  3925.  
  3926.         if (nodeIsElement(node))
  3927.         {
  3928.             if (node->tag->chkattrs)
  3929.                 node->tag->chkattrs(doc, node);
  3930.             else
  3931.                 CheckAttributes(doc, node);
  3932.         }
  3933.  
  3934.         if (node->content)
  3935.             AttributeChecks(doc, node->content);
  3936.  
  3937.         node = next;
  3938.     }
  3939. }
  3940.  
  3941. /*
  3942.   HTML is the top level element
  3943. */
  3944. void ParseDocument(TidyDocImpl* doc)
  3945. {
  3946.     Node *node, *html, *doctype = NULL;
  3947.  
  3948.     while ((node = GetToken(doc, IgnoreWhitespace)) != NULL)
  3949.     {
  3950.         if (node->type == XmlDecl)
  3951.         {
  3952.             if (FindXmlDecl(doc) && doc->root.content)
  3953.             {
  3954.                 ReportError(doc, &doc->root, node, DISCARDING_UNEXPECTED);
  3955.                 FreeNode(doc, node);
  3956.                 continue;
  3957.             }
  3958.             if (node->line != 1 || (node->line == 1 && node->column != 1))
  3959.             {
  3960.                 ReportError(doc, &doc->root, node, SPACE_PRECEDING_XMLDECL);
  3961.             }
  3962.         }
  3963. #ifdef AUTO_INPUT_ENCODING
  3964.         if (node->type == XmlDecl)
  3965.         {
  3966.             AttVal* encoding = GetAttrByName(node, "encoding");
  3967.             if (AttrHasValue(encoding))
  3968.             {
  3969.                 uint id = GetEncodingIdFromName(encoding->value);
  3970.  
  3971.                 /* todo: detect mismatch with BOM/XMLDecl/declared */
  3972.                 /* todo: error for unsupported encodings */
  3973.                 /* todo: try to re-init transcoder */
  3974.                 /* todo: change input/output encoding settings */
  3975.                 /* todo: store id in StreamIn */
  3976.             }
  3977.         }
  3978. #endif /* AUTO_INPUT_ENCODING */
  3979.  
  3980.         /* deal with comments etc. */
  3981.         if (InsertMisc( &doc->root, node ))
  3982.             continue;
  3983.  
  3984.         if (node->type == DocTypeTag)
  3985.         {
  3986.             if (doctype == NULL)
  3987.             {
  3988.                 InsertNodeAtEnd( &doc->root, node);
  3989.                 doctype = node;
  3990.             }
  3991.             else
  3992.             {
  3993.                 ReportError(doc, RootNode, node, DISCARDING_UNEXPECTED);
  3994.                 FreeNode( doc, node);
  3995.             }
  3996.             continue;
  3997.         }
  3998.  
  3999.         if (node->type == EndTag)
  4000.         {
  4001.             ReportError(doc, RootNode, node, DISCARDING_UNEXPECTED);
  4002.             FreeNode( doc, node);
  4003.             continue;
  4004.         }
  4005.  
  4006.         if ( node->type != StartTag || !nodeIsHTML(node) )
  4007.         {
  4008.             UngetToken( doc );
  4009.             html = InferredTag(doc, TidyTag_HTML);
  4010.         }
  4011.         else
  4012.             html = node;
  4013.  
  4014.         if (!FindDocType(doc))
  4015.             ReportError(doc, NULL, NULL, MISSING_DOCTYPE);
  4016.  
  4017.         InsertNodeAtEnd( &doc->root, html);
  4018.         ParseHTML( doc, html, no );
  4019.         break;
  4020.     }
  4021.  
  4022.     if (!FindHTML(doc))
  4023.     {
  4024.         /* a later check should complain if <body> is empty */
  4025.         html = InferredTag(doc, TidyTag_HTML);
  4026.         InsertNodeAtEnd( &doc->root, html);
  4027.         ParseHTML(doc, html, no);
  4028.     }
  4029.  
  4030.     if (!FindTITLE(doc))
  4031.     {
  4032.         Node* head = FindHEAD(doc);
  4033.         ReportError(doc, head, NULL, MISSING_TITLE_ELEMENT);
  4034.         InsertNodeAtEnd(head, InferredTag(doc, TidyTag_TITLE));
  4035.     }
  4036.  
  4037.     AttributeChecks(doc, &doc->root);
  4038.     ReplaceObsoleteElements(doc, &doc->root);
  4039.     DropEmptyElements(doc, &doc->root);
  4040.     CleanSpaces(doc, &doc->root);
  4041.  
  4042.     if (cfgBool(doc, TidyEncloseBodyText))
  4043.         EncloseBodyText(doc);
  4044.     if (cfgBool(doc, TidyEncloseBlockText))
  4045.         EncloseBlockText(doc, &doc->root);
  4046. }
  4047.  
  4048. Bool XMLPreserveWhiteSpace( TidyDocImpl* doc, Node *element)
  4049. {
  4050.     AttVal *attribute;
  4051.  
  4052.     /* search attributes for xml:space */
  4053.     for (attribute = element->attributes; attribute; attribute = attribute->next)
  4054.     {
  4055.         if (AttrValueIs(attribute, "xml:space"))
  4056.         {
  4057.             if (AttrValueIs(attribute, "preserve"))
  4058.                 return yes;
  4059.  
  4060.             return no;
  4061.         }
  4062.     }
  4063.  
  4064.     if (element->element == NULL)
  4065.         return no;
  4066.         
  4067.     /* kludge for html docs without explicit xml:space attribute */
  4068.     if (nodeIsPRE(element)    ||
  4069.         nodeIsSCRIPT(element) ||
  4070.         nodeIsSTYLE(element)  ||
  4071.         FindParser(doc, element) == ParsePre)
  4072.         return yes;
  4073.  
  4074.     /* kludge for XSL docs */
  4075.     if ( tmbstrcasecmp(element->element, "xsl:text") == 0 )
  4076.         return yes;
  4077.  
  4078.     return no;
  4079. }
  4080.  
  4081. /*
  4082.   XML documents
  4083. */
  4084. static void ParseXMLElement(TidyDocImpl* doc, Node *element, uint mode)
  4085. {
  4086.     Lexer* lexer = doc->lexer;
  4087.     Node *node;
  4088.  
  4089.     /* if node is pre or has xml:space="preserve" then do so */
  4090.  
  4091.     if ( XMLPreserveWhiteSpace(doc, element) )
  4092.         mode = Preformatted;
  4093.  
  4094.     while ((node = GetToken(doc, mode)) != NULL)
  4095.     {
  4096.         if (node->type == EndTag && tmbstrcmp(node->element, element->element) == 0)
  4097.         {
  4098.             FreeNode( doc, node);
  4099.             element->closed = yes;
  4100.             break;
  4101.         }
  4102.  
  4103.         /* discard unexpected end tags */
  4104.         if (node->type == EndTag)
  4105.         {
  4106.             if (element)
  4107.                 ReportFatal(doc, element, node, UNEXPECTED_ENDTAG_IN);
  4108.             else
  4109.                 ReportFatal(doc, element, node, UNEXPECTED_ENDTAG);
  4110.  
  4111.             FreeNode( doc, node);
  4112.             continue;
  4113.         }
  4114.  
  4115.         /* parse content on seeing start tag */
  4116.         if (node->type == StartTag)
  4117.             ParseXMLElement( doc, node, mode );
  4118.  
  4119.         InsertNodeAtEnd(element, node);
  4120.     }
  4121.  
  4122.     /*
  4123.      if first child is text then trim initial space and
  4124.      delete text node if it is empty.
  4125.     */
  4126.  
  4127.     node = element->content;
  4128.  
  4129.     if (node && node->type == TextNode && mode != Preformatted)
  4130.     {
  4131.         if ( lexer->lexbuf[node->start] == ' ' )
  4132.         {
  4133.             node->start++;
  4134.  
  4135.             if (node->start >= node->end)
  4136.                 DiscardElement( doc, node );
  4137.         }
  4138.     }
  4139.  
  4140.     /*
  4141.      if last child is text then trim final space and
  4142.      delete the text node if it is empty
  4143.     */
  4144.  
  4145.     node = element->last;
  4146.  
  4147.     if (node && node->type == TextNode && mode != Preformatted)
  4148.     {
  4149.         if ( lexer->lexbuf[node->end - 1] == ' ' )
  4150.         {
  4151.             node->end--;
  4152.  
  4153.             if (node->start >= node->end)
  4154.                 DiscardElement( doc, node );
  4155.         }
  4156.     }
  4157. }
  4158.  
  4159. void ParseXMLDocument(TidyDocImpl* doc)
  4160. {
  4161.     Node *node, *doctype = NULL;
  4162.  
  4163.     SetOptionBool( doc, TidyXmlTags, yes );
  4164.  
  4165.     while ((node = GetToken(doc, IgnoreWhitespace)) != NULL)
  4166.     {
  4167.         /* discard unexpected end tags */
  4168.         if (node->type == EndTag)
  4169.         {
  4170.             ReportError(doc, NULL, node, UNEXPECTED_ENDTAG);
  4171.             FreeNode( doc, node);
  4172.             continue;
  4173.         }
  4174.  
  4175.          /* deal with comments etc. */
  4176.         if (InsertMisc( &doc->root, node))
  4177.             continue;
  4178.  
  4179.         if (node->type == DocTypeTag)
  4180.         {
  4181.             if (doctype == NULL)
  4182.             {
  4183.                 InsertNodeAtEnd( &doc->root, node);
  4184.                 doctype = node;
  4185.             }
  4186.             else
  4187.             {
  4188.                 ReportError(doc, RootNode, node, DISCARDING_UNEXPECTED);
  4189.                 FreeNode( doc, node);
  4190.             }
  4191.             continue;
  4192.         }
  4193.  
  4194.         if (node->type == StartEndTag)
  4195.         {
  4196.             InsertNodeAtEnd( &doc->root, node);
  4197.             continue;
  4198.         }
  4199.  
  4200.        /* if start tag then parse element's content */
  4201.         if (node->type == StartTag)
  4202.         {
  4203.             InsertNodeAtEnd( &doc->root, node );
  4204.             ParseXMLElement( doc, node, IgnoreWhitespace );
  4205.         }
  4206.  
  4207.     }
  4208.  
  4209.     /* ensure presence of initial <?XML version="1.0"?> */
  4210.     if ( cfgBool(doc, TidyXmlDecl) )
  4211.         FixXmlDecl( doc );
  4212. }
  4213.  
  4214.